]> source.dussan.org Git - poi.git/commitdiff
Add NPOIFSFileSystem support for identifying free blocks, along with partial unit...
authorNick Burch <nick@apache.org>
Thu, 23 Dec 2010 08:02:50 +0000 (08:02 +0000)
committerNick Burch <nick@apache.org>
Thu, 23 Dec 2010 08:02:50 +0000 (08:02 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1052194 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/poifs/filesystem/NPOIFSFileSystem.java
src/java/org/apache/poi/poifs/filesystem/NPOIFSStream.java
src/java/org/apache/poi/poifs/storage/BATBlock.java
src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSFileSystem.java
src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSStream.java

index 9a3aed8c9fb2a2cb06248ab98a054403e939785c..d4fb3410379a852d0f5fe32e1ac9b9285f3b9670 100644 (file)
@@ -272,7 +272,9 @@ public class NPOIFSFileSystem
        for(int fatAt : _header.getBATArray()) {
           loopDetector.claim(fatAt);
           ByteBuffer fatData = getBlockAt(fatAt);
-          _bat_blocks.add(BATBlock.createBATBlock(bigBlockSize, fatData));
+          BATBlock bat = BATBlock.createBATBlock(bigBlockSize, fatData);
+          bat.setOurBlockIndex(fatAt);
+          _bat_blocks.add(bat);
        }
        
        // Now read the XFAT blocks
@@ -282,6 +284,7 @@ public class NPOIFSFileSystem
           loopDetector.claim(nextAt);
           ByteBuffer fatData = getBlockAt(nextAt);
           xfat = BATBlock.createBATBlock(bigBlockSize, fatData);
+          xfat.setOurBlockIndex(nextAt);
           nextAt = xfat.getValueAt(bigBlockSize.getNextXBATChainOffset());
           
           _bat_blocks.add(xfat);
@@ -296,8 +299,6 @@ public class NPOIFSFileSystem
      * Load the block at the given offset.
      */
     protected ByteBuffer getBlockAt(final int offset) throws IOException {
-       ByteBuffer data = ByteBuffer.allocate(bigBlockSize.getBigBlockSize());
-       
        // The header block doesn't count, so add one
        long startAt = (offset+1) * bigBlockSize.getBigBlockSize();
        return _data.read(bigBlockSize.getBigBlockSize(), startAt);
@@ -336,9 +337,79 @@ public class NPOIFSFileSystem
      * This method will extend the file if needed, and if doing
      *  so, allocate new FAT blocks to address the extra space.
      */
-    protected int getFreeBlock() {
-       // TODO
-       return -1;
+    protected int getFreeBlock() throws IOException {
+       // First up, do we have any spare ones?
+       int offset = 0;
+       for(int i=0; i<_bat_blocks.size(); i++) {
+          boolean isXBAT = (i >= _header.getBATCount());
+          
+          int numSectors = bigBlockSize.getBATEntriesPerBlock();
+          if(isXBAT) {
+             numSectors = bigBlockSize.getXBATEntriesPerBlock();
+          }
+
+          // Check this one
+          BATBlock bat = _bat_blocks.get(i);
+          if(bat.hasFreeSectors()) {
+             // Claim one of them and return it
+             for(int j=0; j<numSectors; j++) {
+                int batValue = bat.getValueAt(j);
+                if(batValue == POIFSConstants.UNUSED_BLOCK) {
+                   // Bingo
+                   return offset + j;
+                }
+             }
+          }
+          
+          // Move onto the next BAT/XBAT
+          offset += numSectors;
+       }
+       
+       // If we get here, then there aren't any
+       //  free sectors in any of the BATs or XBATs
+       // So, we need to extend the file and add another
+       boolean isBAT = true;
+       if(_header.getBATCount() >= 109) {
+          isBAT = false;
+       }
+       
+       // Create a new BATBlock
+       BATBlock newBAT = BATBlock.createEmptyBATBlock(bigBlockSize, !isBAT);
+       newBAT.setOurBlockIndex(offset);
+       // Ensure there's a spot in the file for it
+       ByteBuffer buffer = ByteBuffer.allocate(bigBlockSize.getBigBlockSize());
+       int writeTo = (1+offset) * bigBlockSize.getBigBlockSize(); // Header isn't in BATs
+       _data.write(buffer, writeTo);
+       
+       // Allocate ourself within ourselves, at the first point
+       if(isBAT) {
+          newBAT.setValueAt(0, POIFSConstants.FAT_SECTOR_BLOCK);
+       } else {
+          newBAT.setValueAt(0, POIFSConstants.DIFAT_SECTOR_BLOCK);
+       }
+
+       // Store us
+       _bat_blocks.add(newBAT);
+       if(isBAT) {
+          // Put it in the BAT array in the header
+          int[] newBATs = new int[_header.getBATCount()+1];
+          System.arraycopy(_header.getBATArray(), 0, newBATs, 0, newBATs.length-1);
+          newBATs[newBATs.length-1] = offset;
+          _header.setBATArray(newBATs);
+          _header.setBATCount(newBATs.length);
+       } else if(_header.getXBATCount() == 0) {
+          // Store our first XBAT offset in the header
+          _header.setXBATStart(offset);
+          _header.setXBATCount(1);
+       } else {
+          // Chain it off the last XBAT
+          BATBlock lastXBAT = _bat_blocks.get(_bat_blocks.size()-1);
+          lastXBAT.setValueAt(bigBlockSize.getNextXBATChainOffset(), offset);
+          _header.setXBATCount(_header.getXBATCount()+1);
+       }
+       
+       // The first offset stores us, but the 2nd is free
+       return offset+1;
     }
 
     /**
index 1739532631c53f0f3ca5ed96cc999e1277e0df65..edffb6b45aedc2772aa7011a7c5f4db3ee33c15f 100644 (file)
@@ -40,7 +40,7 @@ import org.apache.poi.poifs.storage.HeaderBlock;
  *  handle small block ones.
  * This uses the new NIO code
  * 
- * TODO Add loop checking on read and on write
+ * TODO Implement a streaming write method
  */
 
 public class NPOIFSStream implements Iterable<ByteBuffer>
@@ -137,13 +137,18 @@ public class NPOIFSStream implements Iterable<ByteBuffer>
       }
       
       // If we're overwriting, free any remaining blocks
-      // TODO
+      while(nextBlock != POIFSConstants.END_OF_CHAIN) {
+         int thisBlock = nextBlock;
+         loopDetector.claim(thisBlock);
+         nextBlock = filesystem.getNextBlock(thisBlock);
+         filesystem.setNextBlock(thisBlock, POIFSConstants.UNUSED_BLOCK);
+      }
       
       // Mark the end of the stream
       filesystem.setNextBlock(nextBlock, POIFSConstants.END_OF_CHAIN);
    }
    
-   // TODO Streaming write too
+   // TODO Streaming write support too
    
    /**
     * Class that handles a streaming read of one stream
index 3c4bb68a4639c166edae63850ef7b4920a95b37a..636a46f2be461676597e011b62c3bd1bb9679629 100644 (file)
@@ -47,10 +47,14 @@ public final class BATBlock extends BigBlock {
      */
     private boolean _has_free_sectors;
     
+    /**
+     * Where in the file are we?
+     */
+    private int ourBlockIndex;
+    
     /**
      * Create a single instance initialized with default values
      */
-
     private BATBlock(POIFSBigBlockSize bigBlockSize)
     {
         super(bigBlockSize);
@@ -118,6 +122,17 @@ public final class BATBlock extends BigBlock {
        // All done
        return block;
     }
+    
+    /**
+     * Creates a single BATBlock, with all the values set to empty.
+     */
+    public static BATBlock createEmptyBATBlock(final POIFSBigBlockSize bigBlockSize, boolean isXBAT) {
+       BATBlock block = new BATBlock(bigBlockSize);
+       if(isXBAT) {
+          block.setXBATChain(bigBlockSize, POIFSConstants.END_OF_CHAIN);
+       }
+       return block;
+    }
 
     /**
      * Create an array of BATBlocks from an array of int block
@@ -295,10 +310,24 @@ public final class BATBlock extends BigBlock {
           recomputeFree();
        }
     }
+    
+    /**
+     * Record where in the file we live
+     */
+    public void setOurBlockIndex(int index) {
+       this.ourBlockIndex = index;
+    }
+    /**
+     * Retrieve where in the file we live 
+     */
+    public int getOurBlockIndex() {
+       return ourBlockIndex;
+    }
+
 
     /* ********** START extension of BigBlock ********** */
 
-    /**
+   /**
      * Write the block's data to an OutputStream
      *
      * @param stream the OutputStream to which the stored data should
index c511df3741dcc3f3f25ab43c19ee07981a600f90..828315e3db6303bd1103c2cf1ffb3f385d89b6ec 100644 (file)
@@ -146,4 +146,84 @@ public final class TestNPOIFSFileSystem extends TestCase {
       assertEquals((byte)0x00, b.get());
       assertEquals((byte)0x00, b.get());
    }
+   
+   /**
+    * Ask for free blocks where there are some already
+    *  to be had from the FAT
+    */
+   public void testGetFreeBlockWithSpare() throws Exception {
+      NPOIFSFileSystem fs = new NPOIFSFileSystem(_inst.getFile("BlockSize512.zvi"));
+      
+      // Our first BAT block has spares
+      assertEquals(true, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
+      
+      // First free one is 100
+      assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(100));
+      assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(101));
+      assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(102));
+      assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(103));
+      
+      // Ask, will get 100
+      assertEquals(100, fs.getFreeBlock());
+      
+      // Ask again, will still get 100 as not written to
+      assertEquals(100, fs.getFreeBlock());
+      
+      // Allocate it, then ask again
+      fs.setNextBlock(100, POIFSConstants.END_OF_CHAIN);
+      assertEquals(101, fs.getFreeBlock());
+   }
+
+   /**
+    * Ask for free blocks where no free ones exist, and so the
+    *  file needs to be extended and another BAT/XBAT added
+    */
+   public void testGetFreeBlockWithNoneSpare() throws Exception {
+      NPOIFSFileSystem fs = new NPOIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"));
+      
+      // We've spare ones from 100 to 128
+      for(int i=100; i<128; i++) {
+         assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(i));
+      }
+      
+      // Check our BAT knows it's free
+      assertEquals(true, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
+      
+      // Allocate all the spare ones
+      for(int i=100; i<128; i++) {
+         fs.setNextBlock(i, POIFSConstants.END_OF_CHAIN);
+      }
+      
+      // BAT is now full, but there's only the one
+      assertEquals(false, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
+      try {
+         assertEquals(false, fs.getBATBlockAndIndex(128).getBlock().hasFreeSectors());
+         fail("Should only be one BAT");
+      } catch(IndexOutOfBoundsException e) {}
+      
+      // Now ask for a free one, will need to extend the file
+      assertEquals(129, fs.getFreeBlock());
+      
+      assertEquals(false, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
+      assertEquals(true, fs.getBATBlockAndIndex(128).getBlock().hasFreeSectors());
+      assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(128));
+      assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(129));
+      
+      
+      // Fill up to hold 109 BAT blocks
+      // TODO
+      
+      // Ask for another, will get our first XBAT
+      // TODO
+      
+      // Fill the XBAT
+      // TODO
+      
+      // Ask for another, will get our 2nd XBAT
+      // TODO
+      
+      // Write it out and read it back in again
+      // Ensure it's correct
+      // TODO
+   }
 }
index ef792e768a9d3f5c1a245b359468a2150c5c3340..4ace3f1aa9dc305284d6ded66e85bc2fc5794e71 100644 (file)
@@ -26,6 +26,8 @@ import org.apache.poi.POIDataSamples;
 
 /**
  * Tests {@link NPOIFSStream}
+ * 
+ * TODO Write unit tests
  */
 public final class TestNPOIFSStream extends TestCase {
    private static final POIDataSamples _inst = POIDataSamples.getPOIFSInstance();
@@ -211,7 +213,38 @@ public final class TestNPOIFSStream extends TestCase {
     * Craft a nasty file with a loop, and ensure we don't get stuck
     */
    public void testReadFailsOnLoop() throws Exception {
-      // TODO
+      NPOIFSFileSystem fs = new NPOIFSFileSystem(_inst.getFile("BlockSize512.zvi"));
+      
+      // Hack the FAT so that it goes 0->1->2->0
+      fs.setNextBlock(0, 1);
+      fs.setNextBlock(1, 2);
+      fs.setNextBlock(2, 0);
+      
+      // Now try to read
+      NPOIFSStream stream = new NPOIFSStream(fs, 0);
+      Iterator<ByteBuffer> i = stream.getBlockIterator();
+      assertEquals(true, i.hasNext());
+      
+      // 1st read works
+      i.next();
+      assertEquals(true, i.hasNext());
+      
+      // 2nd read works
+      i.next();
+      assertEquals(true, i.hasNext());
+      
+      // 3rd read works
+      i.next();
+      assertEquals(true, i.hasNext());
+      
+      // 4th read blows up as it loops back to 0
+      try {
+         i.next();
+         fail("Loop should have been detected but wasn't!");
+      } catch(RuntimeException e) {
+         // Good, it was detected
+      }
+      assertEquals(true, i.hasNext());
    }
 
    /**