Add NPOIFSFileSystem support for identifying free blocks, along with partial unit tests for it

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1052194 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2010-12-23 08:02:50 +00:00
parent de3d20d51a
commit 9605338d1f
5 changed files with 230 additions and 12 deletions

View 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;
}
/**

View 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

View 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);
@ -119,6 +123,17 @@ public final class BATBlock extends BigBlock {
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
* allocation table entries
@ -296,6 +311,20 @@ public final class BATBlock extends BigBlock {
}
}
/**
* 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 ********** */
/**

View 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
}
}

View 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());
}
/**