diff --git a/src/java/org/apache/poi/poifs/storage/BATBlock.java b/src/java/org/apache/poi/poifs/storage/BATBlock.java index 23188c1c02..3c4bb68a46 100644 --- a/src/java/org/apache/poi/poifs/storage/BATBlock.java +++ b/src/java/org/apache/poi/poifs/storage/BATBlock.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.List; import org.apache.poi.poifs.common.POIFSBigBlockSize; import org.apache.poi.poifs.common.POIFSConstants; @@ -194,7 +195,6 @@ public final class BATBlock extends BigBlock { * * @return the number of BATBlocks needed */ - public static int calculateStorageRequirements(final POIFSBigBlockSize bigBlockSize, final int entryCount) { int _entries_per_block = bigBlockSize.getBATEntriesPerBlock(); @@ -209,14 +209,62 @@ public final class BATBlock extends BigBlock { * * @return the number of XBATBlocks needed */ - public static int calculateXBATStorageRequirements(final POIFSBigBlockSize bigBlockSize, final int entryCount) { int _entries_per_xbat_block = bigBlockSize.getXBATEntriesPerBlock(); return (entryCount + _entries_per_xbat_block - 1) / _entries_per_xbat_block; } + + /** + * Calculates the maximum size of a file which is addressable given the + * number of FAT (BAT and XBAT) sectors specified. + * The actual file size will be between [size of fatCount-1 blocks] and + * [size of fatCount blocks]. + * For 512 byte block sizes, this means we may over-estimate by up to 65kb. + * For 4096 byte block sizes, this means we may over-estimate by up to 4mb + */ + public static int calculateMaximumSize(final POIFSBigBlockSize bigBlockSize, + final int numBAT, final int numXBAT) { + int size = 1; // Header isn't FAT addressed + size += (numBAT * bigBlockSize.getBATEntriesPerBlock()); + size += (numXBAT * bigBlockSize.getXBATEntriesPerBlock()); + return size * bigBlockSize.getBigBlockSize(); + } + public static int calculateMaximumSize(final HeaderBlock header) + { + return calculateMaximumSize(header.getBigBlockSize(), header.getBATCount(), header.getXBATCount()); + } + /** + * Returns the BATBlock that handles the specified offset, + * and the relative index within it. + * The List of BATBlocks must be in sequential order + */ + public static BATBlockAndIndex getBATBlockAndIndex(final int offset, + final HeaderBlock header, final List blocks) { + POIFSBigBlockSize bigBlockSize = header.getBigBlockSize(); + + // Are we in the BAT or XBAT range + int batRangeEndsAt = bigBlockSize.getBATEntriesPerBlock() * + header.getBATCount(); + + if(offset < batRangeEndsAt) { + int whichBAT = (int)Math.floor(offset / bigBlockSize.getBATEntriesPerBlock()); + int index = offset % bigBlockSize.getBATEntriesPerBlock(); + return new BATBlockAndIndex( index, blocks.get(whichBAT) ); + } + + // XBATs hold slightly less + int relOffset = offset - batRangeEndsAt; + int whichXBAT = (int)Math.floor(relOffset / bigBlockSize.getXBATEntriesPerBlock()); + int index = relOffset % bigBlockSize.getXBATEntriesPerBlock(); + return new BATBlockAndIndex( + index, + blocks.get(header.getBATCount() + whichXBAT) + ); + } + private void setXBATChain(final POIFSBigBlockSize bigBlockSize, int chainIndex) { int _entries_per_xbat_block = bigBlockSize.getXBATEntriesPerBlock(); @@ -277,5 +325,21 @@ public final class BATBlock extends BigBlock { } /* ********** END extension of BigBlock ********** */ -} // end public class BATBlock + + + public static class BATBlockAndIndex { + private final int index; + private final BATBlock block; + private BATBlockAndIndex(int index, BATBlock block) { + this.index = index; + this.block = block; + } + public int getIndex() { + return index; + } + public BATBlock getBlock() { + return block; + } + } +} diff --git a/src/testcases/org/apache/poi/poifs/storage/TestBATBlock.java b/src/testcases/org/apache/poi/poifs/storage/TestBATBlock.java index c0b8b88135..82b49e1154 100644 --- a/src/testcases/org/apache/poi/poifs/storage/TestBATBlock.java +++ b/src/testcases/org/apache/poi/poifs/storage/TestBATBlock.java @@ -19,7 +19,10 @@ package org.apache.poi.poifs.storage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.apache.poi.poifs.common.POIFSConstants; @@ -208,4 +211,175 @@ public final class TestBATBlock extends TestCase { public void testGetXBATChainOffset() { assertEquals(508, POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS.getNextXBATChainOffset()); } + + public void testCalculateMaximumSize() throws Exception { + // Zero fat blocks isn't technically valid, but it'd be header only + assertEquals( + 512, + BATBlock.calculateMaximumSize(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS, 0, 0) + ); + assertEquals( + 4096, + BATBlock.calculateMaximumSize(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS, 0, 0) + ); + + // A single FAT block can address 128/1024 blocks + assertEquals( + 512 + 512*128, + BATBlock.calculateMaximumSize(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS, 1, 0) + ); + assertEquals( + 4096 + 4096*1024, + BATBlock.calculateMaximumSize(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS, 1, 0) + ); + + assertEquals( + 512 + 4*512*128, + BATBlock.calculateMaximumSize(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS, 4, 0) + ); + assertEquals( + 4096 + 4*4096*1024, + BATBlock.calculateMaximumSize(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS, 4, 0) + ); + + // Once we get into XBAT blocks, they address a little bit less + assertEquals( + 512 + 109*512*128, + BATBlock.calculateMaximumSize(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS, 109, 0) + ); + assertEquals( + 4096 + 109*4096*1024, + BATBlock.calculateMaximumSize(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS, 109, 0) + ); + + assertEquals( + 512 + 109*512*128 + 512*127, + BATBlock.calculateMaximumSize(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS, 109, 1) + ); + assertEquals( + 4096 + 109*4096*1024 + 4096*1023, + BATBlock.calculateMaximumSize(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS, 109, 1) + ); + + assertEquals( + 512 + 109*512*128 + 3*512*127, + BATBlock.calculateMaximumSize(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS, 109, 3) + ); + assertEquals( + 4096 + 109*4096*1024 + 3*4096*1023, + BATBlock.calculateMaximumSize(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS, 109, 3) + ); + } + + public void testGetBATBlockAndIndex() throws Exception { + HeaderBlock header = new HeaderBlock(POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS); + List blocks = new ArrayList(); + int offset; + + + // First, try a one BAT block file + header.setBATCount(1); + blocks.add( + BATBlock.createBATBlock(header.getBigBlockSize(), ByteBuffer.allocate(512)) + ); + + offset = 0; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1; + assertEquals(1, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 127; + assertEquals(127, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + + // Now go for one with multiple BAT blocks + header.setBATCount(2); + blocks.add( + BATBlock.createBATBlock(header.getBigBlockSize(), ByteBuffer.allocate(512)) + ); + + offset = 0; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 127; + assertEquals(127, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 128; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 129; + assertEquals(1, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + + // And finally one with XBATs too + // This is a naughty file, but we should be able to cope... + // (We'll decide everything is XBAT not BAT) + header.setBATCount(0); + offset = 0; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 126; + assertEquals(126, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 127; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 128; + assertEquals(1, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 129; + assertEquals(2, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + + // Check with the bigger block size too + header = new HeaderBlock(POIFSConstants.LARGER_BIG_BLOCK_SIZE_DETAILS); + + offset = 0; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1022; + assertEquals(1022, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1023; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1024; + assertEquals(1, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + // Biggr block size, back to real BATs + header.setBATCount(2); + + offset = 0; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1022; + assertEquals(1022, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1023; + assertEquals(1023, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(0, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + + offset = 1024; + assertEquals(0, BATBlock.getBATBlockAndIndex(offset, header, blocks).getIndex()); + assertEquals(1, blocks.indexOf( BATBlock.getBATBlockAndIndex(offset, header, blocks).getBlock() )); + } }