/** Constructor, intended for writing */ public NPOIFSFileSystem() { this(true); // Mark us as having a single empty BAT at offset 0 _header.setBATCount(1); _header.setBATArray(new int[] {0}); _bat_blocks.add(BATBlock.createEmptyBATBlock(bigBlockSize, false)); setNextBlock(0, POIFSConstants.FAT_SECTOR_BLOCK); // Now associate the properties with the empty block _property_table.setStartBlock(1); setNextBlock(1, POIFSConstants.END_OF_CHAIN); }
/** * Read and process the PropertiesTable and the 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 NPropertyTable(_header, this); // Finally read the Small Stream FAT (SBAT) blocks BATBlock sfat; List<BATBlock> sbats = new ArrayList<BATBlock>(); _mini_store = new NPOIFSMiniStore(this, _property_table.getRoot(), sbats, _header); nextAt = _header.getSBATStart(); for (int i = 0; i < _header.getSBATCount(); i++) { loopDetector.claim(nextAt); ByteBuffer fatData = getBlockAt(nextAt); sfat = BATBlock.createBATBlock(bigBlockSize, fatData); sfat.setOurBlockIndex(nextAt); sbats.add(sfat); nextAt = getNextBlock(nextAt); } }
/** Has our in-memory objects write their state to their backing blocks */ private void syncWithDataSource() throws IOException { // HeaderBlock HeaderBlockWriter hbw = new HeaderBlockWriter(_header); hbw.writeBlock(getBlockAt(-1)); // BATs for (BATBlock bat : _bat_blocks) { ByteBuffer block = getBlockAt(bat.getOurBlockIndex()); BlockAllocationTableWriter.writeBlock(bat, block); } // SBATs _mini_store.syncWithDataSource(); // Properties _property_table.write(new NPOIFSStream(this, _header.getPropertyStart())); }
/** * Create a POIFSFileSystem from an <tt>InputStream</tt>. Normally the stream is read until EOF. * The stream is always closed. * * <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 * stream in order to trap the <tt>close()</tt> call. A convenience method ( * <tt>createNonClosingInputStream()</tt>) has been provided for this purpose: * * <pre> * InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is); * HSSFWorkbook wb = new HSSFWorkbook(wrappedStream); * is.reset(); * doSomethingElse(is); * </pre> * * Note also the special case of <tt>ByteArrayInputStream</tt> for which the <tt>close()</tt> * method does nothing. * * <pre> * ByteArrayInputStream bais = ... * HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() ! * bais.reset(); // no problem * doSomethingElse(bais); * </pre> * * @param stream the InputStream from which to read the data * @exception IOException on errors reading, or on invalid data */ public NPOIFSFileSystem(InputStream stream) throws IOException { this(false); ReadableByteChannel channel = null; boolean success = false; try { // Turn our InputStream into something NIO based channel = Channels.newChannel(stream); // Get the header ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE); IOUtils.readFully(channel, headerBuffer); // Have the header processed _header = new HeaderBlock(headerBuffer); // Sanity check the block count BlockAllocationTableReader.sanityCheckBlockCount(_header.getBATCount()); // We need to buffer the whole file into memory when // working with an InputStream. // The max possible size is when each BAT block entry is used int maxSize = BATBlock.calculateMaximumSize(_header); ByteBuffer data = ByteBuffer.allocate(maxSize); // Copy in the header headerBuffer.position(0); data.put(headerBuffer); data.position(headerBuffer.capacity()); // Now read the rest of the stream IOUtils.readFully(channel, data); success = true; // Turn it into a DataSource _data = new ByteArrayBackedDataSource(data.array(), data.position()); } finally { // As per the constructor contract, always close the stream if (channel != null) channel.close(); closeInputStream(stream, success); } // Now process the various entries readCoreContents(); }
/** * 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. */ @Override 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++) { int numSectors = bigBlockSize.getBATEntriesPerBlock(); // 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 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); xbat.setValueAt(0, offset); 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()); } // Allocate us in the XBAT for (int i = 0; i < bigBlockSize.getXBATEntriesPerBlock(); i++) { if (xbat.getValueAt(i) == POIFSConstants.UNUSED_BLOCK) { xbat.setValueAt(i, offset); } } } 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; }