mirror of https://github.com/apache/poi.git
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:
parent
de3d20d51a
commit
9605338d1f
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue