Repository: commons-compress Updated Branches: refs/heads/master e926d99b1 -> 09edbd8f1
COMPRESS-327 write support for in memory SeekableByteChannel Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/09edbd8f Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/09edbd8f Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/09edbd8f Branch: refs/heads/master Commit: 09edbd8f1945cd6a2ecc267f4e466862227e2355 Parents: e926d99 Author: Stefan Bodewig <bode...@apache.org> Authored: Thu Oct 13 19:05:57 2016 +0200 Committer: Stefan Bodewig <bode...@apache.org> Committed: Thu Oct 13 19:05:57 2016 +0200 ---------------------------------------------------------------------- .../utils/SeekableInMemoryByteChannel.java | 69 ++++++++++++++++---- .../commons/compress/archivers/ZipTestCase.java | 45 +++++++++++++ .../archivers/sevenz/SevenZOutputFileTest.java | 26 ++++++++ 3 files changed, 129 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/09edbd8f/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java b/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java index ab3a606..87c2111 100644 --- a/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java +++ b/src/main/java/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.java @@ -23,21 +23,27 @@ import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.NonWritableChannelException; import java.nio.channels.SeekableByteChannel; +import java.util.Arrays; /** * A {@link SeekableByteChannel} implementation that wraps a byte[]. + * @since 1.13 */ public class SeekableInMemoryByteChannel implements SeekableByteChannel { - private final byte[] data; + private volatile byte[] data; private volatile boolean closed; - private volatile long position, size; + private volatile int position, size; public SeekableInMemoryByteChannel(byte[] data) { this.data = data; size = data.length; } + public SeekableInMemoryByteChannel() { + this(new byte[0]); + } + @Override public long position() { return position; @@ -45,7 +51,10 @@ public class SeekableInMemoryByteChannel implements SeekableByteChannel { @Override public SeekableByteChannel position(long newPosition) { - position = newPosition; + if (newPosition > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Position cannot exceed " + Integer.MAX_VALUE); + } + position = (int) newPosition; return this; } @@ -56,7 +65,12 @@ public class SeekableInMemoryByteChannel implements SeekableByteChannel { @Override public SeekableByteChannel truncate(long newSize) { - size = newSize; + if (size > newSize) { + size = (int) newSize; + } + if (position > size) { + position = size; + } return this; } @@ -65,14 +79,14 @@ public class SeekableInMemoryByteChannel implements SeekableByteChannel { if (!isOpen()) { throw new ClosedChannelException(); } - long pos = position; - long sz = size; + int pos = position; + int sz = size; int wanted = buf.remaining(); - long possible = sz - pos; + int possible = sz - pos; if (wanted > possible) { - wanted = (int) possible; + wanted = possible; } - buf.put(data, (int) pos, wanted); + buf.put(data, pos, wanted); position = pos + wanted; return wanted; } @@ -87,9 +101,42 @@ public class SeekableInMemoryByteChannel implements SeekableByteChannel { return !closed; } - // TODO implement writing @Override public int write(ByteBuffer b) throws IOException { - throw new NonWritableChannelException(); + if (!isOpen()) { + throw new ClosedChannelException(); + } + int pos = position; + int sz = data.length; + int wanted = b.remaining(); + int possibleWithoutResize = sz - pos; + if (wanted > possibleWithoutResize) { + resize(pos + wanted); + } + b.get(data, pos, wanted); + position = pos + wanted; + if (size < position) { + size = position; + } + return wanted; } + + /** + * Obtains the array backing this channel. + */ + public byte[] array() { + return data; + } + + private void resize(int newLength) { + int len = data.length; + if (len <= 0) { + len = 1; + } + while (len < newLength) { + len <<= 1; + } + data = Arrays.copyOf(data, len); + } + } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/09edbd8f/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java b/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java index 00015ad..84297ae 100644 --- a/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java +++ b/src/test/java/org/apache/commons/compress/archivers/ZipTestCase.java @@ -41,6 +41,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.compress.archivers.zip.ZipMethod; import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.junit.Assert; import org.junit.Test; @@ -109,6 +110,50 @@ public final class ZipTestCase extends AbstractTestCase { } /** + * Archives 2 files and unarchives it again. If the file length of result + * and source is the same, it looks like the operations have worked + * @throws Exception + */ + @Test + public void testZipArchiveCreationInMemory() throws Exception { + final File file1 = getFile("test1.xml"); + final File file2 = getFile("test2.xml"); + SeekableInMemoryByteChannel c = new SeekableInMemoryByteChannel(); + try (ZipArchiveOutputStream os = new ZipArchiveOutputStream(c)) { + os.putArchiveEntry(new ZipArchiveEntry("testdata/test1.xml")); + IOUtils.copy(new FileInputStream(file1), os); + os.closeArchiveEntry(); + + os.putArchiveEntry(new ZipArchiveEntry("testdata/test2.xml")); + IOUtils.copy(new FileInputStream(file2), os); + os.closeArchiveEntry(); + } + + // Unarchive the same + final List<File> results = new ArrayList<>(); + + try (ArchiveInputStream in = new ArchiveStreamFactory() + .createArchiveInputStream("zip", new ByteArrayInputStream(c.array()))) { + + ZipArchiveEntry entry = null; + while((entry = (ZipArchiveEntry)in.getNextEntry()) != null) { + final File outfile = new File(resultDir.getCanonicalPath() + "/result/" + entry.getName()); + outfile.getParentFile().mkdirs(); + try (OutputStream o = new FileOutputStream(outfile)) { + IOUtils.copy(in, o); + } + results.add(outfile); + } + } + + assertEquals(results.size(), 2); + File result = results.get(0); + assertEquals(file1.length(), result.length()); + result = results.get(1); + assertEquals(file2.length(), result.length()); + } + + /** * Simple unarchive test. Asserts nothing. * @throws Exception */ http://git-wip-us.apache.org/repos/asf/commons-compress/blob/09edbd8f/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java index 989aa4d..aee1e02 100644 --- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.Date; import java.util.Iterator; import org.apache.commons.compress.AbstractTestCase; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import org.tukaani.xz.LZMA2Options; public class SevenZOutputFileTest extends AbstractTestCase { @@ -301,6 +302,16 @@ public class SevenZOutputFileTest extends AbstractTestCase { } @Test + public void testStackOfContentCompressionsInMemory() throws Exception { + final ArrayList<SevenZMethodConfiguration> methods = new ArrayList<>(); + methods.add(new SevenZMethodConfiguration(SevenZMethod.LZMA2)); + methods.add(new SevenZMethodConfiguration(SevenZMethod.COPY)); + methods.add(new SevenZMethodConfiguration(SevenZMethod.DEFLATE)); + methods.add(new SevenZMethodConfiguration(SevenZMethod.BZIP2)); + createAndReadBack(new SeekableInMemoryByteChannel(), methods); + } + + @Test public void testDeflateWithConfiguration() throws Exception { output = new File(dir, "deflate-options.7z"); // Deflater.BEST_SPEED @@ -464,6 +475,21 @@ public class SevenZOutputFileTest extends AbstractTestCase { } } + private void createAndReadBack(final SeekableInMemoryByteChannel output, final Iterable<SevenZMethodConfiguration> methods) throws Exception { + final SevenZOutputFile outArchive = new SevenZOutputFile(output); + outArchive.setContentMethods(methods); + try { + addFile(outArchive, 0, true); + } finally { + outArchive.close(); + } + try (SevenZFile archive = + new SevenZFile(new SeekableInMemoryByteChannel(output.array()), "in memory", + null)) { + assertEquals(Boolean.TRUE, verifyFile(archive, 0, methods)); + } + } + private static void assertContentMethodsEquals(final Iterable<? extends SevenZMethodConfiguration> expected, final Iterable<? extends SevenZMethodConfiguration> actual) { assertNotNull(actual);