The NPOIFS-classes result currently in left-over memory mapped buffers.

These are actually hard to workaround, so for Windows the test is ignored, if this error happens.
http://stackoverflow.com/questions/3602783/file-access-synchronized-on-java-object 

Apart of that, the RandomFileAccess instance is saved in the FileBackedDataSource as it needs to
be closed instead of the Channel.
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4796385

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1592418 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2014-05-04 20:58:42 +00:00
parent 4112a8441e
commit 234ad7f85e
3 changed files with 227 additions and 189 deletions

View File

@ -27,7 +27,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PushbackInputStream; import java.io.PushbackInputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@ -162,11 +161,7 @@ public class NPOIFSFileSystem extends BlockStore
public NPOIFSFileSystem(File file, boolean readOnly) public NPOIFSFileSystem(File file, boolean readOnly)
throws IOException throws IOException
{ {
this( this(null, file, readOnly, true);
(new RandomAccessFile(file, readOnly? "r" : "rw")).getChannel(),
readOnly,
true
);
} }
/** /**
@ -184,15 +179,24 @@ public class NPOIFSFileSystem extends BlockStore
public NPOIFSFileSystem(FileChannel channel) public NPOIFSFileSystem(FileChannel channel)
throws IOException throws IOException
{ {
this(channel, false, false); this(channel, null, false, false);
} }
private NPOIFSFileSystem(FileChannel channel, boolean readOnly, boolean closeChannelOnError) private NPOIFSFileSystem(FileChannel channel, File srcFile, boolean readOnly, boolean closeChannelOnError)
throws IOException throws IOException
{ {
this(false); this(false);
try { try {
// Initialize the datasource
if (srcFile != null) {
FileBackedDataSource d = new FileBackedDataSource(srcFile, readOnly);
channel = d.getChannel();
_data = d;
} else {
_data = new FileBackedDataSource(channel, readOnly);
}
// Get the header // Get the header
ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE); ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
IOUtils.readFully(channel, headerBuffer); IOUtils.readFully(channel, headerBuffer);
@ -201,7 +205,6 @@ public class NPOIFSFileSystem extends BlockStore
_header = new HeaderBlock(headerBuffer); _header = new HeaderBlock(headerBuffer);
// Now process the various entries // Now process the various entries
_data = new FileBackedDataSource(channel, readOnly);
readCoreContents(); readCoreContents();
} catch(IOException e) { } catch(IOException e) {
if(closeChannelOnError) { if(closeChannelOnError) {

View File

@ -35,16 +35,22 @@ import org.apache.poi.util.IOUtils;
public class FileBackedDataSource extends DataSource { public class FileBackedDataSource extends DataSource {
private FileChannel channel; private FileChannel channel;
private boolean writable; private boolean writable;
// remember file base, which needs to be closed too
private RandomAccessFile srcFile;
@SuppressWarnings("resource")
public FileBackedDataSource(File file) throws FileNotFoundException { public FileBackedDataSource(File file) throws FileNotFoundException {
if(!file.exists()) { this(newSrcFile(file, "r"), true);
throw new FileNotFoundException(file.toString());
}
this.channel = (new RandomAccessFile(file, "r")).getChannel();
this.writable = false;
} }
public FileBackedDataSource(File file, boolean readOnly) throws FileNotFoundException {
this(newSrcFile(file, readOnly ? "r" : "rw"), readOnly);
}
public FileBackedDataSource(RandomAccessFile srcFile, boolean readOnly) {
this(srcFile.getChannel(), readOnly);
this.srcFile = srcFile;
}
public FileBackedDataSource(FileChannel channel, boolean readOnly) { public FileBackedDataSource(FileChannel channel, boolean readOnly) {
this.channel = channel; this.channel = channel;
this.writable = !readOnly; this.writable = !readOnly;
@ -53,6 +59,10 @@ public class FileBackedDataSource extends DataSource {
public boolean isWriteable() { public boolean isWriteable() {
return this.writable; return this.writable;
} }
public FileChannel getChannel() {
return this.channel;
}
@Override @Override
public ByteBuffer read(int length, long position) throws IOException { public ByteBuffer read(int length, long position) throws IOException {
@ -105,6 +115,18 @@ public class FileBackedDataSource extends DataSource {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
channel.close(); if (srcFile != null) {
// see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4796385
srcFile.close();
} else {
channel.close();
}
}
private static RandomAccessFile newSrcFile(File file, String mode) throws FileNotFoundException {
if(!file.exists()) {
throw new FileNotFoundException(file.toString());
}
return new RandomAccessFile(file, mode);
} }
} }

View File

@ -29,6 +29,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -74,6 +75,7 @@ import org.apache.poi.util.CodePageUtil;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.TempFile; import org.apache.poi.util.TempFile;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -827,179 +829,190 @@ public class TestWrite
*/ */
@Test @Test
public void inPlaceNPOIFSWrite() throws Exception { public void inPlaceNPOIFSWrite() throws Exception {
NPOIFSFileSystem fs = null; try {
DirectoryEntry root = null; NPOIFSFileSystem fs = null;
DocumentNode sinfDoc = null; DirectoryEntry root = null;
DocumentNode dinfDoc = null; DocumentNode sinfDoc = null;
SummaryInformation sinf = null; DocumentNode dinfDoc = null;
DocumentSummaryInformation dinf = null; SummaryInformation sinf = null;
DocumentSummaryInformation dinf = null;
// We need to work on a File for in-place changes, so create a temp one
final File copy = TempFile.createTempFile("Test-HPSF", "ole2"); // We need to work on a File for in-place changes, so create a temp one
copy.deleteOnExit(); final File copy = TempFile.createTempFile("Test-HPSF", "ole2");
copy.deleteOnExit();
// Copy a test file over to our temp location
InputStream inp = _samples.openResourceAsStream("TestShiftJIS.doc"); // Copy a test file over to our temp location
FileOutputStream out = new FileOutputStream(copy); InputStream inp = _samples.openResourceAsStream("TestShiftJIS.doc");
IOUtils.copy(inp, out); FileOutputStream out = new FileOutputStream(copy);
inp.close(); IOUtils.copy(inp, out);
out.close(); inp.close();
out.close();
// Open the copy in read/write mode
fs = new NPOIFSFileSystem(copy, false); // Open the copy in read/write mode
root = fs.getRoot(); fs = new NPOIFSFileSystem(copy, false);
root = fs.getRoot();
// Read the properties in there
sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME); // Read the properties in there
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME); sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion()); sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion());
dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion()); dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion());
// Check they start as we expect
assertEquals("Reiichiro Hori", sinf.getAuthor()); // Check they start as we expect
assertEquals("Microsoft Word 9.0", sinf.getApplicationName()); assertEquals("Reiichiro Hori", sinf.getAuthor());
assertEquals("\u7b2c1\u7ae0", sinf.getTitle()); assertEquals("Microsoft Word 9.0", sinf.getApplicationName());
assertEquals("\u7b2c1\u7ae0", sinf.getTitle());
assertEquals("", dinf.getCompany());
assertEquals(null, dinf.getManager()); assertEquals("", dinf.getCompany());
assertEquals(null, dinf.getManager());
// Do an in-place replace via an InputStream
new NPOIFSDocument(sinfDoc).replaceContents(sinf.toInputStream()); // Do an in-place replace via an InputStream
new NPOIFSDocument(dinfDoc).replaceContents(dinf.toInputStream()); new NPOIFSDocument(sinfDoc).replaceContents(sinf.toInputStream());
new NPOIFSDocument(dinfDoc).replaceContents(dinf.toInputStream());
// Check it didn't get changed
sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME); // Check it didn't get changed
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME); sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion()); sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion());
dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion()); dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion());
// Start again!
fs.close(); // Start again!
inp = _samples.openResourceAsStream("TestShiftJIS.doc"); fs.close();
out = new FileOutputStream(copy); inp = _samples.openResourceAsStream("TestShiftJIS.doc");
IOUtils.copy(inp, out); out = new FileOutputStream(copy);
inp.close(); IOUtils.copy(inp, out);
out.close(); inp.close();
out.close();
fs = new NPOIFSFileSystem(copy, false);
root = fs.getRoot(); fs = new NPOIFSFileSystem(copy, false);
root = fs.getRoot();
// Read the properties in once more
sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME); // Read the properties in once more
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME); sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion()); sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion());
dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion()); dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion());
// Have them write themselves in-place with no changes, as an OutputStream
sinf.write(new NDocumentOutputStream(sinfDoc)); // Have them write themselves in-place with no changes, as an OutputStream
dinf.write(new NDocumentOutputStream(dinfDoc)); sinf.write(new NDocumentOutputStream(sinfDoc));
dinf.write(new NDocumentOutputStream(dinfDoc));
// And also write to some bytes for checking
ByteArrayOutputStream sinfBytes = new ByteArrayOutputStream(); // And also write to some bytes for checking
sinf.write(sinfBytes); ByteArrayOutputStream sinfBytes = new ByteArrayOutputStream();
ByteArrayOutputStream dinfBytes = new ByteArrayOutputStream(); sinf.write(sinfBytes);
dinf.write(dinfBytes); ByteArrayOutputStream dinfBytes = new ByteArrayOutputStream();
dinf.write(dinfBytes);
// Check that the filesystem can give us back the same bytes
sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME); // Check that the filesystem can give us back the same bytes
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME); sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
byte[] sinfData = IOUtils.toByteArray(new NDocumentInputStream(sinfDoc));
byte[] dinfData = IOUtils.toByteArray(new NDocumentInputStream(dinfDoc)); byte[] sinfData = IOUtils.toByteArray(new NDocumentInputStream(sinfDoc));
assertThat(sinfBytes.toByteArray(), equalTo(sinfData)); byte[] dinfData = IOUtils.toByteArray(new NDocumentInputStream(dinfDoc));
assertThat(dinfBytes.toByteArray(), equalTo(dinfData)); assertThat(sinfBytes.toByteArray(), equalTo(sinfData));
assertThat(dinfBytes.toByteArray(), equalTo(dinfData));
// Read back in as-is
sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc)); // Read back in as-is
assertEquals(131077, sinf.getOSVersion()); sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion());
dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion()); dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion());
assertEquals("Reiichiro Hori", sinf.getAuthor());
assertEquals("Microsoft Word 9.0", sinf.getApplicationName()); assertEquals("Reiichiro Hori", sinf.getAuthor());
assertEquals("\u7b2c1\u7ae0", sinf.getTitle()); assertEquals("Microsoft Word 9.0", sinf.getApplicationName());
assertEquals("\u7b2c1\u7ae0", sinf.getTitle());
assertEquals("", dinf.getCompany());
assertEquals(null, dinf.getManager()); assertEquals("", dinf.getCompany());
assertEquals(null, dinf.getManager());
// Now alter a few of them
sinf.setAuthor("Changed Author"); // Now alter a few of them
sinf.setTitle("Le titre \u00e9tait chang\u00e9"); sinf.setAuthor("Changed Author");
dinf.setManager("Changed Manager"); sinf.setTitle("Le titre \u00e9tait chang\u00e9");
dinf.setManager("Changed Manager");
// Save this into the filesystem
sinf.write(new NDocumentOutputStream(sinfDoc)); // Save this into the filesystem
dinf.write(new NDocumentOutputStream(dinfDoc)); sinf.write(new NDocumentOutputStream(sinfDoc));
dinf.write(new NDocumentOutputStream(dinfDoc));
// Read them back in again
sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME); // Read them back in again
sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc)); sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
assertEquals(131077, sinf.getOSVersion()); sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion());
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc)); dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
assertEquals(131077, dinf.getOSVersion()); dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion());
assertEquals("Changed Author", sinf.getAuthor());
assertEquals("Microsoft Word 9.0", sinf.getApplicationName()); assertEquals("Changed Author", sinf.getAuthor());
assertEquals("Le titre \u00e9tait chang\u00e9", sinf.getTitle()); assertEquals("Microsoft Word 9.0", sinf.getApplicationName());
assertEquals("Le titre \u00e9tait chang\u00e9", sinf.getTitle());
assertEquals("", dinf.getCompany());
assertEquals("Changed Manager", dinf.getManager()); assertEquals("", dinf.getCompany());
assertEquals("Changed Manager", dinf.getManager());
// Close the whole filesystem, and open it once more
fs.writeFilesystem(); // Close the whole filesystem, and open it once more
fs.close(); fs.writeFilesystem();
fs.close();
fs = new NPOIFSFileSystem(copy);
root = fs.getRoot(); fs = new NPOIFSFileSystem(copy);
root = fs.getRoot();
// Re-check on load
sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME); // Re-check on load
sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc)); sinfDoc = (DocumentNode)root.getEntry(SummaryInformation.DEFAULT_STREAM_NAME);
assertEquals(131077, sinf.getOSVersion()); sinf = (SummaryInformation)PropertySetFactory.create(new NDocumentInputStream(sinfDoc));
assertEquals(131077, sinf.getOSVersion());
dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc)); dinfDoc = (DocumentNode)root.getEntry(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
assertEquals(131077, dinf.getOSVersion()); dinf = (DocumentSummaryInformation)PropertySetFactory.create(new NDocumentInputStream(dinfDoc));
assertEquals(131077, dinf.getOSVersion());
assertEquals("Changed Author", sinf.getAuthor());
assertEquals("Microsoft Word 9.0", sinf.getApplicationName()); assertEquals("Changed Author", sinf.getAuthor());
assertEquals("Le titre \u00e9tait chang\u00e9", sinf.getTitle()); assertEquals("Microsoft Word 9.0", sinf.getApplicationName());
assertEquals("Le titre \u00e9tait chang\u00e9", sinf.getTitle());
assertEquals("", dinf.getCompany());
assertEquals("Changed Manager", dinf.getManager()); assertEquals("", dinf.getCompany());
assertEquals("Changed Manager", dinf.getManager());
// Tidy up
fs.close(); // Tidy up
copy.delete(); fs.close();
copy.delete();
} catch (FileNotFoundException e) {
// On Windows this might always fail, as the nio classes
// leave memory mapped buffers active, even when the corresponding channel is closed
// The buffers are closed on garbage-collection (but System.gc() can't be forced)
// or via sun.misc.Cleaner, but this is regarded unsafe
// http://stackoverflow.com/questions/2972986
// http://bugs.java.com/view_bug.do?bug_id=4724038
Assume.assumeFalse(System.getProperty("os.name").toLowerCase().contains("win"));
throw e;
}
} }