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
loopDetector.claim(nextAt);
ByteBuffer fatData = getBlockAt(nextAt);
xfat = BATBlock.createBATBlock(bigBlockSize, fatData);
+ xfat.setOurBlockIndex(nextAt);
nextAt = xfat.getValueAt(bigBlockSize.getNextXBATChainOffset());
_bat_blocks.add(xfat);
* 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);
* 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;
}
/**
* 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>
}
// 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
*/
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);
// 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
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
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
+ }
}
/**
* Tests {@link NPOIFSStream}
+ *
+ * TODO Write unit tests
*/
public final class TestNPOIFSStream extends TestCase {
private static final POIDataSamples _inst = POIDataSamples.getPOIFSInstance();
* 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());
}
/**