/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.poifs.filesystem;
import java.io.
ByteArrayInputStream;
import java.io.
Closeable;
import java.io.
File;
import java.io.
FileInputStream;
import java.io.
FileOutputStream;
import java.io.
IOException;
import java.io.
InputStream;
import java.io.
OutputStream;
import java.nio.
ByteBuffer;
import java.nio.channels.
Channels;
import java.nio.channels.
FileChannel;
import java.nio.channels.
ReadableByteChannel;
import java.util.
ArrayList;
import java.util.
Collections;
import java.util.
Iterator;
import java.util.
List;
import org.apache.poi.
EmptyFileException;
import org.apache.poi.poifs.common.
POIFSBigBlockSize;
import org.apache.poi.poifs.common.
POIFSConstants;
import org.apache.poi.poifs.dev.
POIFSViewable;
import org.apache.poi.poifs.nio.
ByteArrayBackedDataSource;
import org.apache.poi.poifs.nio.
DataSource;
import org.apache.poi.poifs.nio.
FileBackedDataSource;
import org.apache.poi.poifs.property.
DirectoryProperty;
import org.apache.poi.poifs.property.
DocumentProperty;
import org.apache.poi.poifs.property.
NPropertyTable;
import org.apache.poi.poifs.storage.
BATBlock;
import org.apache.poi.poifs.storage.
BATBlock.
BATBlockAndIndex;
import org.apache.poi.poifs.storage.
BlockAllocationTableReader;
import org.apache.poi.poifs.storage.
BlockAllocationTableWriter;
import org.apache.poi.poifs.storage.
HeaderBlock;
import org.apache.poi.poifs.storage.
HeaderBlockWriter;
import org.apache.poi.util.
CloseIgnoringInputStream;
import org.apache.poi.util.
IOUtils;
import org.apache.poi.util.
Internal;
import org.apache.poi.util.
POILogFactory;
import org.apache.poi.util.
POILogger;
import org.apache.poi.util.
Removal;
/**
* <p>This is the main class of the POIFS system; it manages the entire
* life cycle of the filesystem.</p>
* <p>This is the new NIO version, which uses less memory</p>
*/
public class
NPOIFSFileSystem extends
BlockStore
implements
POIFSViewable,
Closeable
{
private static final
POILogger LOG =
POILogFactory.
getLogger(
NPOIFSFileSystem.class);
/**
* Convenience method for clients that want to avoid the auto-close behaviour of the constructor.
*/
public static
InputStream createNonClosingInputStream(
InputStream is) {
return new
CloseIgnoringInputStream(
is);
}
private
NPOIFSMiniStore _mini_store;
private
NPropertyTable _property_table;
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
*/
private
POIFSBigBlockSize bigBlockSize =
POIFSConstants.
SMALLER_BIG_BLOCK_SIZE_DETAILS;
private
NPOIFSFileSystem(boolean
newFS)
{
_header = new
HeaderBlock(
bigBlockSize);
_property_table = new
NPropertyTable(
_header);
_mini_store = new
NPOIFSMiniStore(this,
_property_table.
getRoot(), new
ArrayList<
BATBlock>(),
_header);
_xbat_blocks = new
ArrayList<
BATBlock>();
_bat_blocks = new
ArrayList<
BATBlock>();
_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(new byte[
bigBlockSize.
getBigBlockSize()*3]);
}
}
/**
* Constructor, intended for writing
*/
public
NPOIFSFileSystem()
{
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 });
BATBlock bb =
BATBlock.
createEmptyBATBlock(
bigBlockSize, false);
bb.
setOurBlockIndex(1);
_bat_blocks.
add(
bb);
setNextBlock(0,
POIFSConstants.
END_OF_CHAIN);
setNextBlock(1,
POIFSConstants.
FAT_SECTOR_BLOCK);
_property_table.
setStartBlock(0);
}
/**
* <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>
*
* <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
*
* @exception IOException on errors reading, or on invalid data
*/
public
NPOIFSFileSystem(
File file)
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>
*
* <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
*
* @exception IOException on errors reading, or on invalid data
*/
public
NPOIFSFileSystem(
File file, boolean
readOnly)
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>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
*
* @exception IOException on errors reading, or on invalid data
*/
public
NPOIFSFileSystem(
FileChannel channel)
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>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
*
* @exception IOException on errors reading, or on invalid data
*/
public
NPOIFSFileSystem(
FileChannel channel, boolean
readOnly)
throws
IOException
{
this(
channel, null,
readOnly, false);
}
private
NPOIFSFileSystem(
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 e) {
// Until we upgrade to Java 7, and can do a MultiCatch, we
// need to keep these two catch blocks in sync on their cleanup
if (
closeChannelOnError &&
channel != null) {
channel.
close();
channel = null;
}
throw
e;
} catch(
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();
channel = null;
}
throw
e;
}
}
/**
* 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
long
maxSize =
BATBlock.
calculateMaximumSize(
_header);
if (
maxSize >
Integer.
MAX_VALUE) {
throw new
IllegalArgumentException("Unable read a >2gb file via an InputStream");
}
ByteBuffer data =
ByteBuffer.
allocate((int)
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();
}
/**
* @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) {
throw new
RuntimeException(
e);
}
// else not success? Try block did not complete normally
// just print stack trace and leave original ex to be thrown
LOG.
log(
POILogger.
ERROR, "can't close input stream",
e);
}
}
/**
* Checks that the supplied InputStream (which MUST
* support mark and reset) has a POIFS (OLE2) header at the start of it.
* If unsure if your InputStream does support mark / reset,
* use {@link FileMagic#prepareToCheckMagic(InputStream)} to wrap it and make
* sure to always use that, and not the original!
*
* After the method call, the InputStream is at the
* same position as of the time of entering the method.
*
* @param inp An InputStream which supports mark/reset
*
* @deprecated in 3.17-beta2, use {@link FileMagic#valueOf(InputStream)} == {@link FileMagic#OLE2} instead
*/
@
Deprecated
@
Removal(version="4.0")
public static boolean
hasPOIFSHeader(
InputStream inp) throws
IOException {
return
FileMagic.
valueOf(
inp) ==
FileMagic.
OLE2;
}
/**
* Checks if the supplied first 8 bytes of a stream / file
* has a POIFS (OLE2) header.
*
* @deprecated in 3.17-beta2, use {@link FileMagic#valueOf(InputStream)} == {@link FileMagic#OLE2} instead
*/
@
Deprecated
@
Removal(version="4.0")
public static boolean
hasPOIFSHeader(byte[]
header8Bytes) {
try {
return
hasPOIFSHeader(new
ByteArrayInputStream(
header8Bytes));
} catch (
IOException e) {
throw new
RuntimeException("invalid header check",
e);
}
}
/**
* 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() &&
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);
}
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;
}
/**
* 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
*/
@
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);
}
}
/**
* Returns the BATBlock that handles the specified offset,
* and the relative index within it
*/
@
Override
protected
BATBlockAndIndex getBATBlockAndIndex(final int
offset) {
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() );
}
/**
* 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
);
}
/**
* 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 {
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;
}
}
}
// 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);
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());
}
/**
* For unit testing only! Returns the underlying
* properties table
*/
NPropertyTable _get_property_table() {
return
_property_table;
}
/**
* Returns the MiniStore, which performs a similar low
* level function to this, except for the small blocks.
*/
public
NPOIFSMiniStore getMiniStore() {
return
_mini_store;
}
/**
* add a new POIFSDocument to the FileSytem
*
* @param document the POIFSDocument being added
*/
void
addDocument(final
NPOIFSDocument document)
{
_property_table.
addProperty(
document.
getDocumentProperty());
}
/**
* add a new DirectoryProperty to the FileSystem
*
* @param directory the DirectoryProperty being added
*/
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
*
* @return the new DocumentEntry
*
* @exception IOException on error creating the new POIFSDocument
*/
public
DocumentEntry createDocument(final
InputStream stream,
final
String name)
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 writer the writer of the new DocumentEntry
*
* @return the new DocumentEntry
*
* @exception IOException
*/
public
DocumentEntry createDocument(final
String name, final int
size,
final
POIFSWriterListener writer)
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
*/
public
DirectoryEntry createDirectory(final
String name)
throws
IOException
{
return
getRoot().
createDirectory(
name);
}
/**
* Set the contents of a document in the root directory,
* 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
*
* @return the new or updated DocumentEntry
*
* @exception IOException on error populating the POIFSDocument
*/
public
DocumentEntry createOrUpdateDocument(final
InputStream stream,
final
String name)
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.
*/
public boolean
isInPlaceWriteable() {
if(
_data instanceof
FileBackedDataSource) {
if ( ((
FileBackedDataSource)
_data).
isWriteable() ) {
return true;
}
}
return false;
}
/**
* 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
*/
public void
writeFilesystem() throws
IOException
{
if(
_data instanceof
FileBackedDataSource) {
// Good, correct type
} else {
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();
}
/**
* Write the filesystem out
*
* @param stream the OutputStream to which the filesystem will be
* written
*
* @exception 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);
}
/**
* Has our in-memory objects write their state
* 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
NPOIFSStream propStream = new
NPOIFSStream(this,
_header.
getPropertyStart());
_property_table.
preWrite();
_property_table.
write(
propStream);
// _header.setPropertyStart has been updated on write ...
// 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);
}
// XBats
for(
BATBlock bat :
_xbat_blocks) {
ByteBuffer block =
getBlockAt(
bat.
getOurBlockIndex());
BlockAllocationTableWriter.
writeBlock(
bat,
block);
}
}
/**
* Closes the FileSystem, freeing any underlying files, streams
* and buffers. After this, you will be unable to read or
* write from the FileSystem.
*/
public void
close() throws
IOException {
_data.
close();
}
/**
* read in a file and write it back out again
*
* @param args names of the files; arg[ 0 ] is the input file,
* arg[ 1 ] is the output file
*
* @exception IOException
*/
public static void
main(
String args[])
throws
IOException
{
if (
args.length != 2)
{
System.
err.
println(
"two arguments required: input filename and output filename");
System.
exit(1);
}
FileInputStream istream = new
FileInputStream(
args[ 0 ]);
try {
FileOutputStream ostream = new
FileOutputStream(
args[ 1 ]);
try {
NPOIFSFileSystem fs = new
NPOIFSFileSystem(
istream);
try {
fs.
writeFilesystem(
ostream);
} finally {
fs.
close();
}
} finally {
ostream.
close();
}
} finally {
istream.
close();
}
}
/**
* Get the root entry
*
* @return the root entry
*/
public
DirectoryNode getRoot()
{
if (
_root == 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
*/
public
DocumentInputStream createDocumentInputStream(
final
String documentName)
throws
IOException
{
return
getRoot().
createDocumentInputStream(
documentName);
}
/**
* remove an entry
*
* @param entry to be removed
*/
void
remove(
EntryNode entry) throws
IOException
{
// If it's a document, free the blocks
if (
entry instanceof
DocumentEntry) {
NPOIFSDocument doc = new
NPOIFSDocument((
DocumentProperty)
entry.
getProperty(), this);
doc.
free();
}
// Now zap it from the properties list
_property_table.
removeProperty(
entry.
getProperty());
}
/* ********** START begin implementation of POIFSViewable ********** */
/**
* Get an array of objects, some of which may implement
* POIFSViewable
*
* @return an array of Object; may not be null, but may be empty
*/
public
Object []
getViewableArray()
{
if (
preferArray())
{
return ((
POIFSViewable )
getRoot()).
getViewableArray();
}
return new
Object[ 0 ];
}
/**
* Get an Iterator of objects, some of which may implement
* POIFSViewable
*
* @return an Iterator; may not be null, but may have an empty
* back end store
*/
public
Iterator<
Object>
getViewableIterator()
{
if (!
preferArray())
{
return ((
POIFSViewable )
getRoot()).
getViewableIterator();
}
return
Collections.
emptyList().
iterator();
}
/**
* Give viewers a hint as to whether to call getViewableArray or
* getViewableIterator
*
* @return true if a viewer should call getViewableArray, false if
* a viewer should call getViewableIterator
*/
public boolean
preferArray()
{
return ((
POIFSViewable )
getRoot()).
preferArray();
}
/**
* Provides a short description of the object, to be used when a
* POIFSViewable object has not provided its contents.
*
* @return short description
*/
public
String getShortDescription()
{
return "POIFS FileSystem";
}
/* ********** END begin implementation of POIFSViewable ********** */
/**
* @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
*/
public int
getBigBlockSize() {
return
bigBlockSize.
getBigBlockSize();
}
/**
* @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
*/
public
POIFSBigBlockSize getBigBlockSizeDetails() {
return
bigBlockSize;
}
@
Override
protected int
getBlockStoreBlockSize() {
return
getBigBlockSize();
}
@
Internal
public
NPropertyTable getPropertyTable() {
return
_property_table;
}
@
Internal
public
HeaderBlock getHeaderBlock() {
return
_header;
}
}