]> source.dussan.org Git - poi.git/commitdiff
Start to support the MiniStream, by extracting out the BlockStore superclass and...
authorNick Burch <nick@apache.org>
Mon, 27 Dec 2010 06:50:05 +0000 (06:50 +0000)
committerNick Burch <nick@apache.org>
Mon, 27 Dec 2010 06:50:05 +0000 (06:50 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1053007 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/poifs/common/POIFSConstants.java
src/java/org/apache/poi/poifs/filesystem/BlockStore.java [new file with mode: 0644]
src/java/org/apache/poi/poifs/filesystem/NPOIFSFileSystem.java
src/java/org/apache/poi/poifs/filesystem/NPOIFSMiniStore.java [new file with mode: 0644]
src/java/org/apache/poi/poifs/filesystem/NPOIFSStream.java
src/java/org/apache/poi/poifs/storage/BATBlock.java

index 74bc037d135c8c9356d09927de891ee7cbc23e04..b732db45d9626a6e8b5b4c79ce7c7b5bf5a95001 100644 (file)
@@ -33,6 +33,10 @@ public interface POIFSConstants
     public static final POIFSBigBlockSize LARGER_BIG_BLOCK_SIZE_DETAILS = 
        new POIFSBigBlockSize(LARGER_BIG_BLOCK_SIZE, (short)12);
     
+    /** How big a block in the small block stream is. Fixed size */
+    public static final int SMALL_BLOCK_SIZE = 0x0040; 
+    
+    /** How big a single property is */
     public static final int PROPERTY_SIZE  = 0x0080;
     
     /** 
diff --git a/src/java/org/apache/poi/poifs/filesystem/BlockStore.java b/src/java/org/apache/poi/poifs/filesystem/BlockStore.java
new file mode 100644 (file)
index 0000000..1da9b6a
--- /dev/null
@@ -0,0 +1,105 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.filesystem;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.poi.poifs.storage.BATBlock.BATBlockAndIndex;
+
+/**
+ * This abstract class describes a way to read, store, chain
+ *  and free a series of blocks (be they Big or Small ones)
+ */
+public abstract class BlockStore {
+   /**
+    * Returns the size of the blocks managed through the block store.
+    */
+   protected abstract int getBlockStoreBlockSize();
+   
+    /**
+     * Load the block at the given offset.
+     */
+    protected abstract ByteBuffer getBlockAt(final int offset) throws IOException;
+    
+    /**
+     * Extends the file if required to hold blocks up to
+     *  the specified offset, and return the block from there. 
+     */
+    protected abstract ByteBuffer createBlockIfNeeded(final int offset) throws IOException;
+    
+    /**
+     * Returns the BATBlock that handles the specified offset,
+     *  and the relative index within it
+     */
+    protected abstract BATBlockAndIndex getBATBlockAndIndex(final int offset);
+    
+    /**
+     * Works out what block follows the specified one.
+     */
+    protected abstract int getNextBlock(final int offset);
+    
+    /**
+     * Changes the record of what block follows the specified one.
+     */
+    protected abstract void setNextBlock(final int offset, final int nextBlock);
+    
+    /**
+     * Finds a free block, and returns its offset.
+     * This method will extend the file/stream if needed, and if doing
+     *  so, allocate new FAT blocks to address the extra space.
+     */
+    protected abstract int getFreeBlock() throws IOException;
+    
+    /**
+     * Creates a Detector for loops in the chain 
+     */
+    protected abstract ChainLoopDetector getChainLoopDetector() throws IOException;
+    
+    /**
+     * Used to detect if a chain has a loop in it, so
+     *  we can bail out with an error rather than
+     *  spinning away for ever... 
+     */
+    protected class ChainLoopDetector {
+       private boolean[] used_blocks;
+       protected ChainLoopDetector(long rawSize) {
+          int numBlocks = (int)Math.ceil( rawSize / getBlockStoreBlockSize() );
+          used_blocks = new boolean[numBlocks];
+       }
+       protected void claim(int offset) {
+          if(offset >= used_blocks.length) {
+             // They're writing, and have had new blocks requested
+             //  for the write to proceed. That means they're into
+             //  blocks we've allocated for them, so are safe
+             return;
+          }
+          
+          // Claiming an existing block, ensure there's no loop
+          if(used_blocks[offset]) {
+             throw new IllegalStateException(
+                   "Potential loop detected - Block " + offset + 
+                   " was already claimed but was just requested again"
+             );
+          }
+          used_blocks[offset] = true;
+       }
+    }
+}
+
index 3caf3b8dd73e6bc7f1f21f4040d5595e0f5dc553..8147e149c314e7f68dabca60c5a3e982b04aab8d 100644 (file)
@@ -66,7 +66,7 @@ import org.apache.poi.util.POILogger;
  * This is the new NIO version
  */
 
-public class NPOIFSFileSystem
+public class NPOIFSFileSystem extends BlockStore
     implements POIFSViewable
 {
        private static final POILogger _logger =
@@ -79,10 +79,11 @@ public class NPOIFSFileSystem
        return new CloseIgnoringInputStream(is);
     }
    
+    private NPOIFSMiniStore _mini_store;
     private NPropertyTable  _property_table;
-    private List<BATBlock> _bat_blocks;
-    private HeaderBlock    _header;
-    private DirectoryNode  _root;
+    private List<BATBlock>  _bat_blocks;
+    private HeaderBlock     _header;
+    private DirectoryNode   _root;
     
     private DataSource _data;
     
@@ -102,6 +103,7 @@ public class NPOIFSFileSystem
     {
         _header         = new HeaderBlock(bigBlockSize);
         _property_table = new NPropertyTable(_header);
+        _mini_store     = new NPOIFSMiniStore(this, _property_table.getRoot(), new ArrayList<BATBlock>(), _header);
         _bat_blocks     = new ArrayList<BATBlock>();
         _root           = null;
     }
@@ -264,7 +266,7 @@ public class NPOIFSFileSystem
        
        // Each block should only ever be used by one of the
        //  FAT, XFAT or Property Table. Ensure it does
-       ChainLoopDetector loopDetector = new ChainLoopDetector();
+       ChainLoopDetector loopDetector = getChainLoopDetector();
        
        // Read the FAT blocks
        for(int fatAt : _header.getBATArray()) {
@@ -291,6 +293,20 @@ public class NPOIFSFileSystem
        // We're now able to load steams
        // Use this to read in the properties
        _property_table = new NPropertyTable(_header, this);
+       
+       // Finally read the Small Stream FAT (SBAT) blocks
+       BATBlock sfat;
+       List<BATBlock> sbats = new ArrayList<BATBlock>();
+       _mini_store     = new NPOIFSMiniStore(this, _property_table.getRoot(), sbats, _header);
+       nextAt = _header.getSBATStart();
+       for(int i=0; i<_header.getSBATCount(); i++) {
+          loopDetector.claim(nextAt);
+          ByteBuffer fatData = getBlockAt(nextAt);
+          sfat = BATBlock.createBATBlock(bigBlockSize, fatData);
+          sfat.setOurBlockIndex(nextAt);
+          sbats.add(sfat);
+          nextAt = getNextBlock(nextAt);  
+       }
     }
     
     /**
@@ -302,6 +318,24 @@ public class NPOIFSFileSystem
        return _data.read(bigBlockSize.getBigBlockSize(), startAt);
     }
     
+    /**
+     * Load the block at the given offset, 
+     *  extending the file if needed
+     */
+    protected ByteBuffer createBlockIfNeeded(final int offset) throws IOException {
+       try {
+          return getBlockAt(offset);
+       } catch(IndexOutOfBoundsException e) {
+          // The header block doesn't count, so add one
+          long startAt = (offset+1) * bigBlockSize.getBigBlockSize();
+          // Allocate and write
+          ByteBuffer buffer = ByteBuffer.allocate(getBigBlockSize());
+          _data.write(buffer, startAt);
+          // Retrieve the properly backed block
+          return getBlockAt(offset);
+       }
+    }
+    
     /**
      * Returns the BATBlock that handles the specified offset,
      *  and the relative index within it
@@ -409,14 +443,27 @@ public class NPOIFSFileSystem
        // The first offset stores us, but the 2nd is free
        return offset+1;
     }
+    
+    @Override
+    protected ChainLoopDetector getChainLoopDetector() throws IOException {
+      return new ChainLoopDetector(_data.size());
+    }
 
-    /**
+   /**
      * For unit testing only! Returns the underlying
      *  properties table
      */
     NPropertyTable _get_property_table() {
       return _property_table;
     }
+    
+    /**
+     * Returns the MiniStore, which performs a similar low
+     *  level function to this, except for the small blocks.
+     */
+    public NPOIFSMiniStore getMiniStore() {
+       return _mini_store;
+    }
 
    /**
      * Create a new document to be added to the root directory
@@ -725,36 +772,6 @@ public class NPOIFSFileSystem
         }
     }
     
-    /**
-     * Used to detect if a chain has a loop in it, so
-     *  we can bail out with an error rather than
-     *  spinning away for ever... 
-     */
-    protected class ChainLoopDetector {
-       private boolean[] used_blocks;
-       protected ChainLoopDetector() throws IOException {
-          int numBlocks = (int)Math.ceil(_data.size()/bigBlockSize.getBigBlockSize());
-          used_blocks = new boolean[numBlocks];
-       }
-       protected void claim(int offset) {
-          if(offset >= used_blocks.length) {
-             // They're writing, and have had new blocks requested
-             //  for the write to proceed. That means they're into
-             //  blocks we've allocated for them, so are safe
-             return;
-          }
-          
-          // Claiming an existing block, ensure there's no loop
-          if(used_blocks[offset]) {
-             throw new IllegalStateException(
-                   "Potential loop detected - Block " + offset + 
-                   " was already claimed but was just requested again"
-             );
-          }
-          used_blocks[offset] = true;
-       }
-    }
-
     /* ********** START begin implementation of POIFSViewable ********** */
 
     /**
@@ -815,11 +832,13 @@ public class NPOIFSFileSystem
         return "POIFS FileSystem";
     }
 
+    /* **********  END  begin implementation of POIFSViewable ********** */
+
     /**
      * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
      */
     public int getBigBlockSize() {
-       return bigBlockSize.getBigBlockSize();
+      return bigBlockSize.getBigBlockSize();
     }
     /**
      * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
@@ -827,7 +846,8 @@ public class NPOIFSFileSystem
     public POIFSBigBlockSize getBigBlockSizeDetails() {
       return bigBlockSize;
     }
-
-    /* **********  END  begin implementation of POIFSViewable ********** */
+    protected int getBlockStoreBlockSize() {
+       return getBigBlockSize();
+    }
 }
 
diff --git a/src/java/org/apache/poi/poifs/filesystem/NPOIFSMiniStore.java b/src/java/org/apache/poi/poifs/filesystem/NPOIFSMiniStore.java
new file mode 100644 (file)
index 0000000..a34e97e
--- /dev/null
@@ -0,0 +1,195 @@
+
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+
+package org.apache.poi.poifs.filesystem;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.poifs.common.POIFSConstants;
+import org.apache.poi.poifs.property.RootProperty;
+import org.apache.poi.poifs.storage.BATBlock;
+import org.apache.poi.poifs.storage.HeaderBlock;
+import org.apache.poi.poifs.storage.BATBlock.BATBlockAndIndex;
+
+/**
+ * This class handles the MiniStream (small block store)
+ *  in the NIO case for {@link NPOIFSFileSystem}
+ */
+public class NPOIFSMiniStore extends BlockStore
+{
+    private NPOIFSFileSystem _filesystem;
+    private NPOIFSStream     _mini_stream;
+    private List<BATBlock>   _sbat_blocks;
+    private HeaderBlock      _header;
+    private RootProperty     _root;
+
+    protected NPOIFSMiniStore(NPOIFSFileSystem filesystem, RootProperty root,
+         List<BATBlock> sbats, HeaderBlock header)
+    {
+       this._filesystem = filesystem;
+       this._sbat_blocks = sbats;
+       this._header = header;
+       this._root = root;
+       
+       this._mini_stream = new NPOIFSStream(filesystem);
+    }
+    
+    /**
+     * Load the block at the given offset.
+     */
+    protected ByteBuffer getBlockAt(final int offset) throws IOException {
+       // Which big block is this?
+       int byteOffset = offset * POIFSConstants.SMALL_BLOCK_SIZE;
+       int bigBlockNumber = byteOffset / _filesystem.getBigBlockSize();
+       int bigBlockOffset = byteOffset % _filesystem.getBigBlockSize();
+       
+       // Now locate the data block for it
+       Iterator<ByteBuffer> it = _mini_stream.getBlockIterator();
+       for(int i=0; i<bigBlockNumber; i++) {
+          it.next();
+       }
+       ByteBuffer dataBlock = it.next();
+       
+       // Skip forward to the right place
+       dataBlock.position(bigBlockOffset);
+       
+       // All done
+       return dataBlock;
+    }
+    
+    /**
+     * Load the block, extending the underlying stream if needed
+     */
+    protected ByteBuffer createBlockIfNeeded(final int offset) throws IOException {
+       // TODO Extend
+       return getBlockAt(offset);
+    }
+    
+    /**
+     * Returns the BATBlock that handles the specified offset,
+     *  and the relative index within it
+     */
+    protected BATBlockAndIndex getBATBlockAndIndex(final int offset) {
+       return BATBlock.getSBATBlockAndIndex(
+             offset, _header, _sbat_blocks
+       );
+    }
+    
+    /**
+     * Works out what block follows the specified one.
+     */
+    protected int getNextBlock(final int offset) {
+       BATBlockAndIndex bai = getBATBlockAndIndex(offset);
+       return bai.getBlock().getValueAt( bai.getIndex() );
+    }
+    
+    /**
+     * Changes the record of what block follows the specified one.
+     */
+    protected void setNextBlock(final int offset, final int nextBlock) {
+       BATBlockAndIndex bai = getBATBlockAndIndex(offset);
+       bai.getBlock().setValueAt(
+             bai.getIndex(), nextBlock
+       );
+    }
+    
+    /**
+     * Finds a free block, and returns its offset.
+     * This method will extend the file if needed, and if doing
+     *  so, allocate new FAT blocks to address the extra space.
+     */
+    protected int getFreeBlock() throws IOException {
+       int sectorsPerSBAT = _filesystem.getBigBlockSizeDetails().getBATEntriesPerBlock();
+       
+       // First up, do we have any spare ones?
+       int offset = 0;
+       for(int i=0; i<_sbat_blocks.size(); i++) {
+          // Check this one
+          BATBlock sbat = _sbat_blocks.get(i);
+          if(sbat.hasFreeSectors()) {
+             // Claim one of them and return it
+             for(int j=0; j<sectorsPerSBAT; j++) {
+                int sbatValue = sbat.getValueAt(j);
+                if(sbatValue == POIFSConstants.UNUSED_BLOCK) {
+                   // Bingo
+                   return offset + j;
+                }
+             }
+          }
+          
+          // Move onto the next SBAT
+          offset += sectorsPerSBAT;
+       }
+       
+       // If we get here, then there aren't any
+       //  free sectors in any of the SBATs
+       // So, we need to extend the chain and add another
+       
+       // Create a new BATBlock
+       BATBlock newSBAT = BATBlock.createEmptyBATBlock(_filesystem.getBigBlockSizeDetails(), false);
+       int batForSBAT = _filesystem.getFreeBlock();
+       newSBAT.setOurBlockIndex(batForSBAT);
+       
+       // Are we the first SBAT?
+       if(_header.getSBATCount() == 0) {
+          _header.setSBATStart(batForSBAT);
+          _header.setSBATBlockCount(1);
+       } else {
+          // Find the end of the SBAT stream, and add the sbat in there
+          ChainLoopDetector loopDetector = _filesystem.getChainLoopDetector();
+          int batOffset = _header.getSBATStart();
+          while(true) {
+             loopDetector.claim(batOffset);
+             int nextBat = _filesystem.getNextBlock(batOffset);
+             if(nextBat == POIFSConstants.END_OF_CHAIN) {
+                break;
+             }
+             batOffset = nextBat;
+          }
+          
+          // Add it in at the end
+          _filesystem.setNextBlock(batOffset, batForSBAT);
+          
+          // And update the count
+          _header.setSBATBlockCount(
+                _header.getSBATCount() + 1
+          );
+       }
+       
+       // Finish allocating
+       _filesystem.setNextBlock(batForSBAT, POIFSConstants.END_OF_CHAIN);
+       _sbat_blocks.add(newSBAT);
+       
+       // Return our first spot
+       return offset;
+    }
+    
+    @Override
+    protected ChainLoopDetector getChainLoopDetector() throws IOException {
+      return new ChainLoopDetector( _root.getSize() );
+    }
+
+    protected int getBlockStoreBlockSize() {
+       return POIFSConstants.SMALL_BLOCK_SIZE;
+    }
+}
+
index 947729f060c64b1b1c6fd7f25e4a1a6962b29388..859bde146967b93deb6eed0a10f195e74be3c632 100644 (file)
@@ -24,7 +24,7 @@ import java.nio.ByteBuffer;
 import java.util.Iterator;
 
 import org.apache.poi.poifs.common.POIFSConstants;
-import org.apache.poi.poifs.filesystem.NPOIFSFileSystem.ChainLoopDetector;
+import org.apache.poi.poifs.filesystem.BlockStore.ChainLoopDetector;
 import org.apache.poi.poifs.property.Property;
 import org.apache.poi.poifs.storage.HeaderBlock;
 
@@ -45,7 +45,7 @@ import org.apache.poi.poifs.storage.HeaderBlock;
 
 public class NPOIFSStream implements Iterable<ByteBuffer>
 {
-       private NPOIFSFileSystem filesystem;
+       private BlockStore blockStore;
        private int startBlock;
        
        /**
@@ -53,8 +53,8 @@ public class NPOIFSStream implements Iterable<ByteBuffer>
         *  to know how to get the start block (eg from a 
         *  {@link HeaderBlock} or a {@link Property}) 
         */
-       public NPOIFSStream(NPOIFSFileSystem filesystem, int startBlock) {
-          this.filesystem = filesystem;
+       public NPOIFSStream(BlockStore blockStore, int startBlock) {
+          this.blockStore = blockStore;
           this.startBlock = startBlock;
        }
        
@@ -62,8 +62,8 @@ public class NPOIFSStream implements Iterable<ByteBuffer>
         * Constructor for a new stream. A start block won't
         *  be allocated until you begin writing to it.
         */
-       public NPOIFSStream(NPOIFSFileSystem filesystem) {
-          this.filesystem = filesystem;
+       public NPOIFSStream(BlockStore blockStore) {
+      this.blockStore = blockStore;
           this.startBlock = POIFSConstants.END_OF_CHAIN;
        }
        
@@ -101,51 +101,56 @@ public class NPOIFSStream implements Iterable<ByteBuffer>
     */
    public void updateContents(byte[] contents) throws IOException {
       // How many blocks are we going to need?
-      int blocks = (int)Math.ceil(contents.length / filesystem.getBigBlockSize());
+      int blockSize = blockStore.getBlockStoreBlockSize();
+      int blocks = (int)Math.ceil(contents.length / blockSize);
       
       // Make sure we don't encounter a loop whilst overwriting
       //  the existing blocks
-      ChainLoopDetector loopDetector = filesystem.new ChainLoopDetector();
+      ChainLoopDetector loopDetector = blockStore.getChainLoopDetector();
       
       // Start writing
       int prevBlock = POIFSConstants.END_OF_CHAIN;
       int nextBlock = startBlock;
       for(int i=0; i<blocks; i++) {
          int thisBlock = nextBlock;
-         loopDetector.claim(thisBlock);
          
          // Allocate a block if needed, otherwise figure
          //  out what the next block will be
          if(thisBlock == POIFSConstants.END_OF_CHAIN) {
-            thisBlock = filesystem.getFreeBlock();
+            thisBlock = blockStore.getFreeBlock();
+            loopDetector.claim(thisBlock);
+            
+            // We're on the end of the chain
             nextBlock = POIFSConstants.END_OF_CHAIN;
             
-            // Mark the previous block as carrying on
+            // Mark the previous block as carrying on to us if needed
             if(prevBlock != POIFSConstants.END_OF_CHAIN) {
-               filesystem.setNextBlock(prevBlock, thisBlock);
+               blockStore.setNextBlock(prevBlock, thisBlock);
             }
          } else {
-            nextBlock = filesystem.getNextBlock(thisBlock);
+            loopDetector.claim(thisBlock);
+            nextBlock = blockStore.getNextBlock(thisBlock);
          }
          
          // Write it
-         ByteBuffer buffer = filesystem.getBlockAt(thisBlock);
-         buffer.put(contents, i*filesystem.getBigBlockSize(), filesystem.getBigBlockSize());
+         ByteBuffer buffer = blockStore.createBlockIfNeeded(thisBlock);
+         buffer.put(contents, i*blockSize, blockSize);
          
          // Update pointers
          prevBlock = thisBlock;
       }
+      int lastBlock = prevBlock;
       
       // If we're overwriting, free any remaining blocks
       while(nextBlock != POIFSConstants.END_OF_CHAIN) {
          int thisBlock = nextBlock;
          loopDetector.claim(thisBlock);
-         nextBlock = filesystem.getNextBlock(thisBlock);
-         filesystem.setNextBlock(thisBlock, POIFSConstants.UNUSED_BLOCK);
+         nextBlock = blockStore.getNextBlock(thisBlock);
+         blockStore.setNextBlock(thisBlock, POIFSConstants.UNUSED_BLOCK);
       }
       
       // Mark the end of the stream
-      filesystem.setNextBlock(nextBlock, POIFSConstants.END_OF_CHAIN);
+      blockStore.setNextBlock(lastBlock, POIFSConstants.END_OF_CHAIN);
    }
    
    // TODO Streaming write support too
@@ -160,7 +165,7 @@ public class NPOIFSStream implements Iterable<ByteBuffer>
       protected StreamBlockByteBufferIterator(int firstBlock) {
          this.nextBlock = firstBlock;
          try {
-            this.loopDetector = filesystem.new ChainLoopDetector();
+            this.loopDetector = blockStore.getChainLoopDetector();
          } catch(IOException e) {
             throw new RuntimeException(e);
          }
@@ -180,8 +185,8 @@ public class NPOIFSStream implements Iterable<ByteBuffer>
          
          try {
             loopDetector.claim(nextBlock);
-            ByteBuffer data = filesystem.getBlockAt(nextBlock);
-            nextBlock = filesystem.getNextBlock(nextBlock);
+            ByteBuffer data = blockStore.getBlockAt(nextBlock);
+            nextBlock = blockStore.getNextBlock(nextBlock);
             return data;
          } catch(IOException e) {
             throw new RuntimeException(e);
index 636a46f2be461676597e011b62c3bd1bb9679629..a48b43aa2c37674749d39daa322bb5ec4bef5cbd 100644 (file)
@@ -257,7 +257,7 @@ public final class BATBlock extends BigBlock {
      * The List of BATBlocks must be in sequential order
      */
     public static BATBlockAndIndex getBATBlockAndIndex(final int offset, 
-                final HeaderBlock header, final List<BATBlock> blocks) {
+                final HeaderBlock header, final List<BATBlock> bats) {
        POIFSBigBlockSize bigBlockSize = header.getBigBlockSize();
        
        // Are we in the BAT or XBAT range
@@ -267,7 +267,7 @@ public final class BATBlock extends BigBlock {
        if(offset < batRangeEndsAt) {
           int whichBAT = (int)Math.floor(offset / bigBlockSize.getBATEntriesPerBlock());
           int index = offset % bigBlockSize.getBATEntriesPerBlock();
-          return new BATBlockAndIndex( index, blocks.get(whichBAT) );
+          return new BATBlockAndIndex( index, bats.get(whichBAT) );
        }
        
        // XBATs hold slightly less
@@ -276,10 +276,25 @@ public final class BATBlock extends BigBlock {
        int index = relOffset % bigBlockSize.getXBATEntriesPerBlock();
        return new BATBlockAndIndex(
              index,
-             blocks.get(header.getBATCount() + whichXBAT)
+             bats.get(header.getBATCount() + whichXBAT)
        );
     }
     
+    /**
+     * Returns the BATBlock that handles the specified offset,
+     *  and the relative index within it, for the mini stream.
+     * The List of BATBlocks must be in sequential order
+     */
+    public static BATBlockAndIndex getSBATBlockAndIndex(final int offset, 
+          final HeaderBlock header, final List<BATBlock> sbats) {
+       POIFSBigBlockSize bigBlockSize = header.getBigBlockSize();
+       
+       // SBATs are so much easier, as they're chained streams
+       int whichSBAT = (int)Math.floor(offset / bigBlockSize.getBATEntriesPerBlock());
+       int index = offset % bigBlockSize.getBATEntriesPerBlock();
+       return new BATBlockAndIndex( index, sbats.get(whichSBAT) );
+    }
+    
     private void setXBATChain(final POIFSBigBlockSize bigBlockSize, int chainIndex)
     {
         int _entries_per_xbat_block = bigBlockSize.getXBATEntriesPerBlock();