*/
public class POIFSFileSystem extends BlockStore
- implements POIFSViewable, Closeable
-{
+ implements POIFSViewable, Closeable {
//arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000;
/**
* Maximum number size (in blocks) of the allocation table as supported by
* POI.<p>
- *
+ * <p>
* This constant has been chosen to help POI identify corrupted data in the
* header block (rather than crash immediately with {@link OutOfMemoryError}
* ). It's not clear if the compound document format actually specifies any
private POIFSMiniStore _mini_store;
private PropertyTable _property_table;
- private List<BATBlock> _xbat_blocks;
- private List<BATBlock> _bat_blocks;
- private HeaderBlock _header;
- private DirectoryNode _root;
-
+ private List<BATBlock> _xbat_blocks;
+ private List<BATBlock> _bat_blocks;
+ private HeaderBlock _header;
+ private DirectoryNode _root;
+
private DataSource _data;
-
+
/**
* What big block size the file uses. Most files
- * use 512 bytes, but a few use 4096
+ * use 512 bytes, but a few use 4096
*/
- private POIFSBigBlockSize bigBlockSize =
- POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS;
+ private POIFSBigBlockSize bigBlockSize =
+ POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS;
- private POIFSFileSystem(boolean newFS)
- {
- _header = new HeaderBlock(bigBlockSize);
+ private POIFSFileSystem(boolean newFS) {
+ _header = new HeaderBlock(bigBlockSize);
_property_table = new PropertyTable(_header);
- _mini_store = new POIFSMiniStore(this, _property_table.getRoot(), new ArrayList<>(), _header);
- _xbat_blocks = new ArrayList<>();
- _bat_blocks = new ArrayList<>();
- _root = null;
-
- if(newFS) {
- // Data needs to initially hold just the header block,
- // a single bat block, and an empty properties section
- _data = new ByteArrayBackedDataSource(IOUtils.safelyAllocate(
- bigBlockSize.getBigBlockSize()*3, MAX_RECORD_LENGTH));
+ _mini_store = new POIFSMiniStore(this, _property_table.getRoot(), new ArrayList<>(), _header);
+ _xbat_blocks = new ArrayList<>();
+ _bat_blocks = new ArrayList<>();
+ _root = null;
+
+ if (newFS) {
+ // Data needs to initially hold just the header block,
+ // a single bat block, and an empty properties section
+ _data = new ByteArrayBackedDataSource(IOUtils.safelyAllocate(
+ bigBlockSize.getBigBlockSize() * 3, MAX_RECORD_LENGTH));
}
}
-
+
/**
* Constructor, intended for writing
*/
- public POIFSFileSystem()
- {
- this(true);
-
+ public POIFSFileSystem() {
+ this(true);
+
// Reserve block 0 for the start of the Properties Table
// Create a single empty BAT, at pop that at offset 1
_header.setBATCount(1);
- _header.setBATArray(new int[] { 1 });
+ _header.setBATArray(new int[]{1});
BATBlock bb = BATBlock.createEmptyBATBlock(bigBlockSize, false);
bb.setOurBlockIndex(1);
_bat_blocks.add(bb);
/**
* <p>Creates a POIFSFileSystem from a <tt>File</tt>. This uses less memory than
- * creating from an <tt>InputStream</tt>. The File will be opened read-only</p>
- *
+ * creating from an <tt>InputStream</tt>. The File will be opened read-only</p>
+ *
* <p>Note that with this constructor, you will need to call {@link #close()}
- * when you're done to have the underlying file closed, as the file is
- * kept open during normal operation to read the data out.</p>
- *
- * @param file the File from which to read the data
+ * when you're done to have the underlying file closed, as the file is
+ * kept open during normal operation to read the data out.</p>
*
- * @exception IOException on errors reading, or on invalid data
+ * @param file the File from which to read the data
+ * @throws IOException on errors reading, or on invalid data
*/
public POIFSFileSystem(File file)
- throws IOException
- {
- this(file, true);
+ throws IOException {
+ this(file, true);
}
-
+
/**
* <p>Creates a POIFSFileSystem from a <tt>File</tt>. This uses less memory than
- * creating from an <tt>InputStream</tt>.</p>
- *
+ * creating from an <tt>InputStream</tt>.</p>
+ *
* <p>Note that with this constructor, you will need to call {@link #close()}
- * when you're done to have the underlying file closed, as the file is
- * kept open during normal operation to read the data out.</p>
- *
- * @param file the File from which to read or read/write the data
- * @param readOnly whether the POIFileSystem will only be used in read-only mode
+ * when you're done to have the underlying file closed, as the file is
+ * kept open during normal operation to read the data out.</p>
*
- * @exception IOException on errors reading, or on invalid data
+ * @param file the File from which to read or read/write the data
+ * @param readOnly whether the POIFileSystem will only be used in read-only mode
+ * @throws IOException on errors reading, or on invalid data
*/
public POIFSFileSystem(File file, boolean readOnly)
- throws IOException
- {
- this(null, file, readOnly, true);
+ throws IOException {
+ this(null, file, readOnly, true);
}
-
+
/**
- * <p>Creates a POIFSFileSystem from an open <tt>FileChannel</tt>. This uses
- * less memory than creating from an <tt>InputStream</tt>. The stream will
- * be used in read-only mode.</p>
- *
+ * <p>Creates a POIFSFileSystem from an open <tt>FileChannel</tt>. This uses
+ * less memory than creating from an <tt>InputStream</tt>. The stream will
+ * be used in read-only mode.</p>
+ *
* <p>Note that with this constructor, you will need to call {@link #close()}
- * when you're done to have the underlying Channel closed, as the channel is
- * kept open during normal operation to read the data out.</p>
- *
- * @param channel the FileChannel from which to read the data
+ * when you're done to have the underlying Channel closed, as the channel is
+ * kept open during normal operation to read the data out.</p>
*
- * @exception IOException on errors reading, or on invalid data
+ * @param channel the FileChannel from which to read the data
+ * @throws IOException on errors reading, or on invalid data
*/
public POIFSFileSystem(FileChannel channel)
- throws IOException
- {
- this(channel, true);
+ throws IOException {
+ this(channel, true);
}
-
+
/**
- * <p>Creates a POIFSFileSystem from an open <tt>FileChannel</tt>. This uses
- * less memory than creating from an <tt>InputStream</tt>.</p>
- *
+ * <p>Creates a POIFSFileSystem from an open <tt>FileChannel</tt>. This uses
+ * less memory than creating from an <tt>InputStream</tt>.</p>
+ *
* <p>Note that with this constructor, you will need to call {@link #close()}
- * when you're done to have the underlying Channel closed, as the channel is
- * kept open during normal operation to read the data out.</p>
- *
- * @param channel the FileChannel from which to read or read/write the data
- * @param readOnly whether the POIFileSystem will only be used in read-only mode
+ * when you're done to have the underlying Channel closed, as the channel is
+ * kept open during normal operation to read the data out.</p>
*
- * @exception IOException on errors reading, or on invalid data
+ * @param channel the FileChannel from which to read or read/write the data
+ * @param readOnly whether the POIFileSystem will only be used in read-only mode
+ * @throws IOException on errors reading, or on invalid data
*/
public POIFSFileSystem(FileChannel channel, boolean readOnly)
- throws IOException
- {
- this(channel, null, readOnly, false);
+ throws IOException {
+ this(channel, null, readOnly, false);
}
-
+
private POIFSFileSystem(FileChannel channel, File srcFile, boolean readOnly, boolean closeChannelOnError)
- throws IOException
- {
- this(false);
-
- try {
- // Initialize the datasource
- if (srcFile != null) {
- if (srcFile.length() == 0)
- throw new EmptyFileException();
-
- FileBackedDataSource d = new FileBackedDataSource(srcFile, readOnly);
- channel = d.getChannel();
- _data = d;
- } else {
- _data = new FileBackedDataSource(channel, readOnly);
- }
-
- // Get the header
- ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
- IOUtils.readFully(channel, headerBuffer);
-
- // Have the header processed
- _header = new HeaderBlock(headerBuffer);
-
- // Now process the various entries
- readCoreContents();
- } catch(IOException | RuntimeException e) {
- // Comes from Iterators etc.
- // TODO Decide if we can handle these better whilst
- // still sticking to the iterator contract
- if (closeChannelOnError && channel != null) {
- channel.close();
- }
- throw e;
- }
- }
-
+ throws IOException {
+ this(false);
+
+ try {
+ // Initialize the datasource
+ if (srcFile != null) {
+ if (srcFile.length() == 0)
+ throw new EmptyFileException();
+
+ FileBackedDataSource d = new FileBackedDataSource(srcFile, readOnly);
+ channel = d.getChannel();
+ _data = d;
+ } else {
+ _data = new FileBackedDataSource(channel, readOnly);
+ }
+
+ // Get the header
+ ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
+ IOUtils.readFully(channel, headerBuffer);
+
+ // Have the header processed
+ _header = new HeaderBlock(headerBuffer);
+
+ // Now process the various entries
+ readCoreContents();
+ } catch (IOException | RuntimeException e) {
+ // Comes from Iterators etc.
+ // TODO Decide if we can handle these better whilst
+ // still sticking to the iterator contract
+ if (closeChannelOnError && channel != null) {
+ channel.close();
+ }
+ throw e;
+ }
+ }
+
/**
* Create a POIFSFileSystem from an <tt>InputStream</tt>. Normally the stream is read until
* EOF. The stream is always closed.<p>
- *
+ * <p>
* Some streams are usable after reaching EOF (typically those that return <code>true</code>
* for <tt>markSupported()</tt>). In the unlikely case that the caller has such a stream
* <i>and</i> needs to use it after this constructor completes, a work around is to wrap the
* </pre>
*
* @param stream the InputStream from which to read the data
- *
- * @exception IOException on errors reading, or on invalid data
+ * @throws IOException on errors reading, or on invalid data
*/
public POIFSFileSystem(InputStream stream)
- throws IOException
- {
+ throws IOException {
this(false);
boolean success = false;
// As per the constructor contract, always close the stream
closeInputStream(stream, success);
}
-
+
// Now process the various entries
readCoreContents();
}
+
/**
- * @param stream the stream to be closed
+ * @param stream the stream to be closed
* @param success <code>false</code> if an exception is currently being thrown in the calling method
*/
private void closeInputStream(InputStream stream, boolean success) {
try {
stream.close();
} catch (IOException e) {
- if(success) {
+ if (success) {
throw new RuntimeException(e);
}
// else not success? Try block did not complete normally
/**
* Read and process the PropertiesTable and the
- * FAT / XFAT blocks, so that we're ready to
- * work with the file
+ * FAT / XFAT blocks, so that we're ready to
+ * work with the file
*/
private void readCoreContents() throws IOException {
- // Grab the block size
- bigBlockSize = _header.getBigBlockSize();
-
- // Each block should only ever be used by one of the
- // FAT, XFAT or Property Table. Ensure it does
- ChainLoopDetector loopDetector = getChainLoopDetector();
-
- // Read the FAT blocks
- for(int fatAt : _header.getBATArray()) {
- readBAT(fatAt, loopDetector);
- }
-
- // Work out how many FAT blocks remain in the XFATs
- int remainingFATs = _header.getBATCount() - _header.getBATArray().length;
-
- // Now read the XFAT blocks, and the FATs within them
- BATBlock xfat;
- int nextAt = _header.getXBATIndex();
- for(int i=0; i<_header.getXBATCount(); i++) {
- loopDetector.claim(nextAt);
- ByteBuffer fatData = getBlockAt(nextAt);
- xfat = BATBlock.createBATBlock(bigBlockSize, fatData);
- xfat.setOurBlockIndex(nextAt);
- nextAt = xfat.getValueAt(bigBlockSize.getXBATEntriesPerBlock());
- _xbat_blocks.add(xfat);
-
- // Process all the (used) FATs from this XFAT
- int xbatFATs = Math.min(remainingFATs, bigBlockSize.getXBATEntriesPerBlock());
- for(int j=0; j<xbatFATs; j++) {
- int fatAt = xfat.getValueAt(j);
- if(fatAt == POIFSConstants.UNUSED_BLOCK || fatAt == POIFSConstants.END_OF_CHAIN) break;
- readBAT(fatAt, loopDetector);
- }
- remainingFATs -= xbatFATs;
- }
-
- // We're now able to load steams
- // Use this to read in the properties
- _property_table = new PropertyTable(_header, this);
-
- // Finally read the Small Stream FAT (SBAT) blocks
- BATBlock sfat;
- List<BATBlock> sbats = new ArrayList<>();
- _mini_store = new POIFSMiniStore(this, _property_table.getRoot(), sbats, _header);
- nextAt = _header.getSBATStart();
- for(int i=0; i<_header.getSBATCount() && nextAt != POIFSConstants.END_OF_CHAIN; i++) {
- loopDetector.claim(nextAt);
- ByteBuffer fatData = getBlockAt(nextAt);
- sfat = BATBlock.createBATBlock(bigBlockSize, fatData);
- sfat.setOurBlockIndex(nextAt);
- sbats.add(sfat);
- nextAt = getNextBlock(nextAt);
- }
+ // Grab the block size
+ bigBlockSize = _header.getBigBlockSize();
+
+ // Each block should only ever be used by one of the
+ // FAT, XFAT or Property Table. Ensure it does
+ ChainLoopDetector loopDetector = getChainLoopDetector();
+
+ // Read the FAT blocks
+ for (int fatAt : _header.getBATArray()) {
+ readBAT(fatAt, loopDetector);
+ }
+
+ // Work out how many FAT blocks remain in the XFATs
+ int remainingFATs = _header.getBATCount() - _header.getBATArray().length;
+
+ // Now read the XFAT blocks, and the FATs within them
+ BATBlock xfat;
+ int nextAt = _header.getXBATIndex();
+ for (int i = 0; i < _header.getXBATCount(); i++) {
+ loopDetector.claim(nextAt);
+ ByteBuffer fatData = getBlockAt(nextAt);
+ xfat = BATBlock.createBATBlock(bigBlockSize, fatData);
+ xfat.setOurBlockIndex(nextAt);
+ nextAt = xfat.getValueAt(bigBlockSize.getXBATEntriesPerBlock());
+ _xbat_blocks.add(xfat);
+
+ // Process all the (used) FATs from this XFAT
+ int xbatFATs = Math.min(remainingFATs, bigBlockSize.getXBATEntriesPerBlock());
+ for (int j = 0; j < xbatFATs; j++) {
+ int fatAt = xfat.getValueAt(j);
+ if (fatAt == POIFSConstants.UNUSED_BLOCK || fatAt == POIFSConstants.END_OF_CHAIN) break;
+ readBAT(fatAt, loopDetector);
+ }
+ remainingFATs -= xbatFATs;
+ }
+
+ // We're now able to load steams
+ // Use this to read in the properties
+ _property_table = new PropertyTable(_header, this);
+
+ // Finally read the Small Stream FAT (SBAT) blocks
+ BATBlock sfat;
+ List<BATBlock> sbats = new ArrayList<>();
+ _mini_store = new POIFSMiniStore(this, _property_table.getRoot(), sbats, _header);
+ nextAt = _header.getSBATStart();
+ for (int i = 0; i < _header.getSBATCount() && nextAt != POIFSConstants.END_OF_CHAIN; i++) {
+ loopDetector.claim(nextAt);
+ ByteBuffer fatData = getBlockAt(nextAt);
+ sfat = BATBlock.createBATBlock(bigBlockSize, fatData);
+ sfat.setOurBlockIndex(nextAt);
+ sbats.add(sfat);
+ nextAt = getNextBlock(nextAt);
+ }
}
+
private void readBAT(int batAt, ChainLoopDetector loopDetector) throws IOException {
- loopDetector.claim(batAt);
- ByteBuffer fatData = getBlockAt(batAt);
- BATBlock bat = BATBlock.createBATBlock(bigBlockSize, fatData);
- bat.setOurBlockIndex(batAt);
- _bat_blocks.add(bat);
+ loopDetector.claim(batAt);
+ ByteBuffer fatData = getBlockAt(batAt);
+ BATBlock bat = BATBlock.createBATBlock(bigBlockSize, fatData);
+ bat.setOurBlockIndex(batAt);
+ _bat_blocks.add(bat);
}
+
private BATBlock createBAT(int offset, boolean isBAT) throws IOException {
- // 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);
- // All done
- return newBAT;
- }
-
+ // 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);
+ // All done
+ return newBAT;
+ }
+
/**
* Load the block at the given offset.
*/
@Override
protected ByteBuffer getBlockAt(final int offset) throws IOException {
- // The header block doesn't count, so add one
- long blockWanted = offset + 1L;
- long startAt = blockWanted * bigBlockSize.getBigBlockSize();
- try {
- return _data.read(bigBlockSize.getBigBlockSize(), startAt);
- } catch (IndexOutOfBoundsException e) {
- IndexOutOfBoundsException wrapped = new IndexOutOfBoundsException("Block " + offset + " not found");
- wrapped.initCause(e);
- throw wrapped;
- }
- }
-
- /**
- * Load the block at the given offset,
- * extending the file if needed
+ // The header block doesn't count, so add one
+ long blockWanted = offset + 1L;
+ long startAt = blockWanted * bigBlockSize.getBigBlockSize();
+ try {
+ return _data.read(bigBlockSize.getBigBlockSize(), startAt);
+ } catch (IndexOutOfBoundsException e) {
+ IndexOutOfBoundsException wrapped = new IndexOutOfBoundsException("Block " + offset + " not found");
+ wrapped.initCause(e);
+ throw wrapped;
+ }
+ }
+
+ /**
+ * Load the block at the given offset,
+ * extending the file if needed
*/
@Override
protected ByteBuffer createBlockIfNeeded(final int offset) throws IOException {
- try {
- return getBlockAt(offset);
- } catch(IndexOutOfBoundsException e) {
- // The header block doesn't count, so add one
- long startAt = (offset+1L) * bigBlockSize.getBigBlockSize();
- // Allocate and write
- ByteBuffer buffer = ByteBuffer.allocate(getBigBlockSize());
- _data.write(buffer, startAt);
- // Retrieve the properly backed block
- return getBlockAt(offset);
- }
- }
-
+ try {
+ return getBlockAt(offset);
+ } catch (IndexOutOfBoundsException e) {
+ // The header block doesn't count, so add one
+ long startAt = (offset + 1L) * bigBlockSize.getBigBlockSize();
+ // Allocate and write
+ ByteBuffer buffer = ByteBuffer.allocate(getBigBlockSize());
+ _data.write(buffer, startAt);
+ // Retrieve the properly backed block
+ return getBlockAt(offset);
+ }
+ }
+
/**
* Returns the BATBlock that handles the specified offset,
- * and the relative index within it
+ * and the relative index within it
*/
@Override
protected BATBlockAndIndex getBATBlockAndIndex(final int offset) {
- return BATBlock.getBATBlockAndIndex(
- offset, _header, _bat_blocks
- );
+ return BATBlock.getBATBlockAndIndex(
+ offset, _header, _bat_blocks
+ );
}
-
+
/**
* Works out what block follows the specified one.
*/
@Override
protected int getNextBlock(final int offset) {
- BATBlockAndIndex bai = getBATBlockAndIndex(offset);
- return bai.getBlock().getValueAt( bai.getIndex() );
+ BATBlockAndIndex bai = getBATBlockAndIndex(offset);
+ return bai.getBlock().getValueAt(bai.getIndex());
}
-
+
/**
* Changes the record of what block follows the specified one.
*/
@Override
protected void setNextBlock(final int offset, final int nextBlock) {
- BATBlockAndIndex bai = getBATBlockAndIndex(offset);
- bai.getBlock().setValueAt(
- bai.getIndex(), nextBlock
- );
+ BATBlockAndIndex bai = getBATBlockAndIndex(offset);
+ bai.getBlock().setValueAt(
+ bai.getIndex(), nextBlock
+ );
}
-
+
/**
* Finds a free block, and returns its offset.
* This method will extend the file if needed, and if doing
- * so, allocate new FAT blocks to address the extra space.
+ * so, allocate new FAT blocks to address the extra space.
*/
@Override
protected int getFreeBlock() throws IOException {
int numSectors = bigBlockSize.getBATEntriesPerBlock();
- // First up, do we have any spare ones?
- int offset = 0;
- for (BATBlock bat : _bat_blocks) {
- 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;
+ // First up, do we have any spare ones?
+ int offset = 0;
+ for (BATBlock bat : _bat_blocks) {
+ 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
- offset += numSectors;
- }
-
- // If we get here, then there aren't any free sectors
- // in any of the BATs, so we need another BAT
- BATBlock bat = createBAT(offset, true);
- bat.setValueAt(0, POIFSConstants.FAT_SECTOR_BLOCK);
- _bat_blocks.add(bat);
-
- // Now store a reference to the BAT in the required place
- if(_header.getBATCount() >= 109) {
- // Needs to come from an XBAT
- BATBlock xbat = null;
- for(BATBlock x : _xbat_blocks) {
- if(x.hasFreeSectors()) {
- xbat = x;
- break;
- }
- }
- if(xbat == null) {
- // Oh joy, we need a new XBAT too...
- xbat = createBAT(offset+1, false);
- // Allocate our new BAT as the first block in the XBAT
- xbat.setValueAt(0, offset);
- // And allocate the XBAT in the BAT
- bat.setValueAt(1, POIFSConstants.DIFAT_SECTOR_BLOCK);
-
- // Will go one place higher as XBAT added in
- offset++;
-
- // Chain it
- if(_xbat_blocks.size() == 0) {
- _header.setXBATStart(offset);
- } else {
- _xbat_blocks.get(_xbat_blocks.size()-1).setValueAt(
- bigBlockSize.getXBATEntriesPerBlock(), offset
- );
- }
- _xbat_blocks.add(xbat);
- _header.setXBATCount(_xbat_blocks.size());
- } else {
- // Allocate our BAT in the existing XBAT with space
- for(int i=0; i<bigBlockSize.getXBATEntriesPerBlock(); i++) {
- if(xbat.getValueAt(i) == POIFSConstants.UNUSED_BLOCK) {
- xbat.setValueAt(i, offset);
+ }
+
+ // Move onto the next BAT
+ offset += numSectors;
+ }
+
+ // If we get here, then there aren't any free sectors
+ // in any of the BATs, so we need another BAT
+ BATBlock bat = createBAT(offset, true);
+ bat.setValueAt(0, POIFSConstants.FAT_SECTOR_BLOCK);
+ _bat_blocks.add(bat);
+
+ // Now store a reference to the BAT in the required place
+ if (_header.getBATCount() >= 109) {
+ // Needs to come from an XBAT
+ BATBlock xbat = null;
+ for (BATBlock x : _xbat_blocks) {
+ if (x.hasFreeSectors()) {
+ xbat = x;
break;
- }
- }
- }
- } else {
- // Store us 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(_bat_blocks.size());
-
- // The current offset stores us, but the next one is free
- return offset+1;
- }
-
+ }
+ }
+ if (xbat == null) {
+ // Oh joy, we need a new XBAT too...
+ xbat = createBAT(offset + 1, false);
+ // Allocate our new BAT as the first block in the XBAT
+ xbat.setValueAt(0, offset);
+ // And allocate the XBAT in the BAT
+ bat.setValueAt(1, POIFSConstants.DIFAT_SECTOR_BLOCK);
+
+ // Will go one place higher as XBAT added in
+ offset++;
+
+ // Chain it
+ if (_xbat_blocks.size() == 0) {
+ _header.setXBATStart(offset);
+ } else {
+ _xbat_blocks.get(_xbat_blocks.size() - 1).setValueAt(
+ bigBlockSize.getXBATEntriesPerBlock(), offset
+ );
+ }
+ _xbat_blocks.add(xbat);
+ _header.setXBATCount(_xbat_blocks.size());
+ } else {
+ // Allocate our BAT in the existing XBAT with space
+ for (int i = 0; i < bigBlockSize.getXBATEntriesPerBlock(); i++) {
+ if (xbat.getValueAt(i) == POIFSConstants.UNUSED_BLOCK) {
+ xbat.setValueAt(i, offset);
+ break;
+ }
+ }
+ }
+ } else {
+ // Store us 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(_bat_blocks.size());
+
+ // The current offset stores us, but the next one is free
+ return offset + 1;
+ }
+
protected long size() throws IOException {
return _data.size();
}
-
+
@Override
protected ChainLoopDetector getChainLoopDetector() throws IOException {
- return new ChainLoopDetector(_data.size());
+ return new ChainLoopDetector(_data.size());
}
- /**
+ /**
* For unit testing only! Returns the underlying
- * properties table
+ * properties table
*/
PropertyTable _get_property_table() {
- return _property_table;
+ return _property_table;
}
-
+
/**
* Returns the MiniStore, which performs a similar low
- * level function to this, except for the small blocks.
+ * level function to this, except for the small blocks.
*/
POIFSMiniStore getMiniStore() {
- return _mini_store;
+ return _mini_store;
}
/**
- * add a new POIFSDocument to the FileSytem
+ * add a new POIFSDocument to the FileSytem
*
* @param document the POIFSDocument being added
*/
- void addDocument(final POIFSDocument document)
- {
+ void addDocument(final POIFSDocument document) {
_property_table.addProperty(document.getDocumentProperty());
}
*
* @param directory the DirectoryProperty being added
*/
- void addDirectory(final DirectoryProperty directory)
- {
+ void addDirectory(final DirectoryProperty directory) {
_property_table.addProperty(directory);
}
- /**
+ /**
* Create a new document to be added to the root directory
*
* @param stream the InputStream from which the document's data
* will be obtained
- * @param name the name of the new POIFSDocument
- *
+ * @param name the name of the new POIFSDocument
* @return the new DocumentEntry
- *
- * @exception IOException on error creating the new POIFSDocument
+ * @throws IOException on error creating the new POIFSDocument
*/
public DocumentEntry createDocument(final InputStream stream,
final String name)
- throws IOException
- {
+ throws IOException {
return getRoot().createDocument(name, stream);
}
* create a new DocumentEntry in the root entry; the data will be
* provided later
*
- * @param name the name of the new DocumentEntry
- * @param size the size of the new DocumentEntry
+ * @param name the name of the new DocumentEntry
+ * @param size the size of the new DocumentEntry
* @param writer the writer of the new DocumentEntry
- *
* @return the new DocumentEntry
- *
- * @exception IOException if the writer exceeds the given size
+ * @throws IOException if the writer exceeds the given size
*/
public DocumentEntry createDocument(final String name, final int size, final POIFSWriterListener writer)
- throws IOException {
+ throws IOException {
return getRoot().createDocument(name, size, writer);
}
* create a new DirectoryEntry in the root directory
*
* @param name the name of the new DirectoryEntry
- *
* @return the new DirectoryEntry
- *
- * @exception IOException on name duplication
+ * @throws IOException on name duplication
*/
public DirectoryEntry createDirectory(final String name)
- throws IOException
- {
+ throws IOException {
return getRoot().createDirectory(name);
}
-
+
/**
* Set the contents of a document in the root directory,
- * creating if needed, otherwise updating
+ * creating if needed, otherwise updating
*
* @param stream the InputStream from which the document's data
* will be obtained
- * @param name the name of the new or existing POIFSDocument
- *
+ * @param name the name of the new or existing POIFSDocument
* @return the new or updated DocumentEntry
- *
- * @exception IOException on error populating the POIFSDocument
+ * @throws IOException on error populating the POIFSDocument
*/
@SuppressWarnings("UnusedReturnValue")
public DocumentEntry createOrUpdateDocument(final InputStream stream, final String name)
- throws IOException {
+ throws IOException {
return getRoot().createOrUpdateDocument(name, stream);
}
-
+
/**
* Does the filesystem support an in-place write via
- * {@link #writeFilesystem()} ? If false, only writing out to
- * a brand new file via {@link #writeFilesystem(OutputStream)}
- * is supported.
+ * {@link #writeFilesystem()} ? If false, only writing out to
+ * a brand new file via {@link #writeFilesystem(OutputStream)}
+ * is supported.
*/
public boolean isInPlaceWriteable() {
return (_data instanceof FileBackedDataSource) && ((FileBackedDataSource) _data).isWriteable();
}
-
+
/**
* Write the filesystem out to the open file. Will thrown an
- * {@link IllegalArgumentException} if opened from an
- * {@link InputStream}.
- *
- * @exception IOException thrown on errors writing to the stream
+ * {@link IllegalArgumentException} if opened from an
+ * {@link InputStream}.
+ *
+ * @throws IOException thrown on errors writing to the stream
*/
public void writeFilesystem() throws IOException {
- if (!(_data instanceof FileBackedDataSource)) {
- throw new IllegalArgumentException(
- "POIFS opened from an inputstream, so writeFilesystem() may " +
- "not be called. Use writeFilesystem(OutputStream) instead"
- );
- }
- if (! ((FileBackedDataSource)_data).isWriteable()) {
- throw new IllegalArgumentException(
- "POIFS opened in read only mode, so writeFilesystem() may " +
- "not be called. Open the FileSystem in read-write mode first"
- );
- }
- syncWithDataSource();
+ if (!(_data instanceof FileBackedDataSource)) {
+ throw new IllegalArgumentException(
+ "POIFS opened from an inputstream, so writeFilesystem() may " +
+ "not be called. Use writeFilesystem(OutputStream) instead"
+ );
+ }
+ if (!((FileBackedDataSource) _data).isWriteable()) {
+ throw new IllegalArgumentException(
+ "POIFS opened in read only mode, so writeFilesystem() may " +
+ "not be called. Open the FileSystem in read-write mode first"
+ );
+ }
+ syncWithDataSource();
}
/**
*
* @param stream the OutputStream to which the filesystem will be
* written
- *
- * @exception IOException thrown on errors writing to the stream
+ * @throws IOException thrown on errors writing to the stream
*/
public void writeFilesystem(final OutputStream stream) throws IOException {
- // Have the datasource updated
- syncWithDataSource();
-
- // Now copy the contents to the stream
- _data.copyTo(stream);
+ // Have the datasource updated
+ syncWithDataSource();
+
+ // Now copy the contents to the stream
+ _data.copyTo(stream);
}
-
+
/**
* Has our in-memory objects write their state
- * to their backing blocks
+ * to their backing blocks
*/
private void syncWithDataSource() throws IOException {
// Mini Stream + SBATs first, as mini-stream details have
// to be stored in the Root Property
_mini_store.syncWithDataSource();
-
+
// Properties
POIFSStream propStream = new POIFSStream(this, _header.getPropertyStart());
_property_table.preWrite();
_property_table.write(propStream);
// _header.setPropertyStart has been updated on write ...
-
+
// HeaderBlock
ByteArrayOutputStream baos = new ByteArrayOutputStream(
_header.getBigBlockSize().getBigBlockSize()
_header.writeData(baos);
getBlockAt(-1).put(baos.toByteArray());
-
- // BATs
- for(BATBlock bat : _bat_blocks) {
- ByteBuffer block = getBlockAt(bat.getOurBlockIndex());
- bat.writeData(block);
- }
- // XBats
- for(BATBlock bat : _xbat_blocks) {
- ByteBuffer block = getBlockAt(bat.getOurBlockIndex());
- bat.writeData(block);
+
+ // BATs
+ for (BATBlock bat : _bat_blocks) {
+ ByteBuffer block = getBlockAt(bat.getOurBlockIndex());
+ bat.writeData(block);
+ }
+ // XBats
+ for (BATBlock bat : _xbat_blocks) {
+ ByteBuffer block = getBlockAt(bat.getOurBlockIndex());
+ bat.writeData(block);
}
}
-
+
/**
* Closes the FileSystem, freeing any underlying files, streams
- * and buffers. After this, you will be unable to read or
- * write from the FileSystem.
+ * and buffers. After this, you will be unable to read or
+ * write from the FileSystem.
*/
public void close() throws IOException {
- _data.close();
+ _data.close();
}
/**
public static void main(String[] args) throws IOException {
if (args.length != 2) {
System.err.println(
- "two arguments required: input filename and output filename");
+ "two arguments required: input filename and output filename");
System.exit(1);
}
*/
public DirectoryNode getRoot() {
if (_root == null) {
- _root = new DirectoryNode(_property_table.getRoot(), this, null);
+ _root = new DirectoryNode(_property_table.getRoot(), this, null);
}
return _root;
}
* open a document in the root entry's list of entries
*
* @param documentName the name of the document to be opened
- *
* @return a newly opened DocumentInputStream
- *
- * @exception IOException if the document does not exist or the
- * name is that of a DirectoryEntry
+ * @throws IOException if the document does not exist or the
+ * name is that of a DirectoryEntry
*/
public DocumentInputStream createDocumentInputStream(
final String documentName) throws IOException {
- return getRoot().createDocumentInputStream(documentName);
+ return getRoot().createDocumentInputStream(documentName);
}
/**
void remove(EntryNode entry) throws IOException {
// If it's a document, free the blocks
if (entry instanceof DocumentEntry) {
- POIFSDocument doc = new POIFSDocument((DocumentProperty)entry.getProperty(), this);
+ POIFSDocument doc = new POIFSDocument((DocumentProperty) entry.getProperty(), this);
doc.free();
}
-
+
// Now zap it from the properties list
_property_table.removeProperty(entry.getProperty());
}
-
+
/* ********** START begin implementation of POIFSViewable ********** */
/**
*
* @return an array of Object; may not be null, but may be empty
*/
- public Object [] getViewableArray() {
+ public Object[] getViewableArray() {
if (preferArray()) {
return getRoot().getViewableArray();
}
- return new Object[ 0 ];
+ return new Object[0];
}
/**
* getViewableIterator
*
* @return true if a viewer should call getViewableArray, false if
- * a viewer should call getViewableIterator
+ * a viewer should call getViewableIterator
*/
public boolean preferArray() {
* @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
*/
public int getBigBlockSize() {
- return bigBlockSize.getBigBlockSize();
+ return bigBlockSize.getBigBlockSize();
}
/**
*/
@SuppressWarnings("WeakerAccess")
public POIFSBigBlockSize getBigBlockSizeDetails() {
- return bigBlockSize;
+ return bigBlockSize;
}
/**
* Creates a new {@link POIFSFileSystem} in a new {@link File}.
* Use {@link #POIFSFileSystem(File)} to open an existing File,
- * this should only be used to create a new empty filesystem.
+ * this should only be used to create a new empty filesystem.
*
* @param file The file to create and open
* @return The created and opened {@link POIFSFileSystem}
@Override
protected int getBlockStoreBlockSize() {
- return getBigBlockSize();
+ return getBigBlockSize();
}
@Internal