COMPRESS-374 support writing LZMA in 7z archives
Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/9238eab6 Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/9238eab6 Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/9238eab6 Branch: refs/heads/master Commit: 9238eab678395743bce4bd273c4534d10f77f715 Parents: e8cee81 Author: Stefan Bodewig <[email protected]> Authored: Tue Nov 29 22:27:58 2016 +0100 Committer: Stefan Bodewig <[email protected]> Committed: Tue Nov 29 22:27:58 2016 +0100 ---------------------------------------------------------------------- src/changes/changes.xml | 4 + .../compress/archivers/sevenz/Coders.java | 16 --- .../compress/archivers/sevenz/LZMADecoder.java | 121 +++++++++++++++++++ src/site/xdoc/examples.xml | 10 +- src/site/xdoc/index.xml | 5 +- src/site/xdoc/limitations.xml | 7 +- .../compress/archivers/SevenZTestCase.java | 5 + .../sevenz/SevenZMethodConfigurationTest.java | 18 +++ .../archivers/sevenz/SevenZOutputFileTest.java | 16 +++ 9 files changed, 176 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c7d67ca..e25e4c6 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -76,6 +76,10 @@ The <action> type attribute can be add,update,fix,remove. Add write support for the legacy LZMA format, this requires XZ for Java 1.6. </action> + <action issue="COMPRESS-374" type="add" date="2016-11-29"> + Add write support for the legacy LZMA stream to 7z, this + requires XZ for Java 1.6. + </action> </release> <release version="1.12" date="2016-06-21" description="Release 1.12 - API compatible to 1.11 but requires Java 6 at runtime. http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java index 1d7244b..b36895c 100644 --- a/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java @@ -99,22 +99,6 @@ class Coders { } } - static class LZMADecoder extends CoderBase { - @Override - InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, - final Coder coder, final byte[] password) throws IOException { - final byte propsByte = coder.properties[0]; - long dictSize = coder.properties[1]; - for (int i = 1; i < 4; i++) { - dictSize |= (coder.properties[i + 1] & 0xffl) << (8 * i); - } - if (dictSize > LZMAInputStream.DICT_SIZE_MAX) { - throw new IOException("Dictionary larger than 4GiB maximum size used in " + archiveName); - } - return new LZMAInputStream(in, uncompressedLength, propsByte, (int) dictSize); - } - } - static class BCJDecoder extends CoderBase { private final FilterOptions opts; BCJDecoder(final FilterOptions opts) { http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java b/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java new file mode 100644 index 0000000..78e1fc8 --- /dev/null +++ b/src/main/java/org/apache/commons/compress/archivers/sevenz/LZMADecoder.java @@ -0,0 +1,121 @@ +/* + * 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.commons.compress.archivers.sevenz; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.tukaani.xz.LZMA2Options; +import org.tukaani.xz.LZMAInputStream; +import org.tukaani.xz.LZMAOutputStream; +import org.tukaani.xz.UnsupportedOptionsException; + +class LZMADecoder extends CoderBase { + LZMADecoder() { + super(LZMA2Options.class, Number.class); + } + + @Override + InputStream decode(final String archiveName, final InputStream in, final long uncompressedLength, + final Coder coder, final byte[] password) throws IOException { + final byte propsByte = coder.properties[0]; + final int dictSize = getDictionarySize(coder); + if (dictSize > LZMAInputStream.DICT_SIZE_MAX) { + throw new IOException("Dictionary larger than 4GiB maximum size used in " + archiveName); + } + return new LZMAInputStream(in, uncompressedLength, propsByte, (int) dictSize); + } + + @Override + OutputStream encode(final OutputStream out, final Object opts) + throws IOException { + return new FilterOutputStream(new LZMAOutputStream(out, getOptions(opts), false)) { + @Override + public void flush() { + // NOOP as LZMAOutputStream throws an exception in flush + } + }; + } + + @Override + byte[] getOptionsAsProperties(final Object opts) { + final LZMA2Options options = getOptions(opts); + final byte props = (byte) ((options.getPb() * 5 + options.getLp()) * 9 + options.getLc()); + int dictSize = options.getDictSize(); + return new byte[] { + props, + (byte) (dictSize & 0xff), + (byte) ((dictSize >> 8) & 0xff), + (byte) ((dictSize >> 16) & 0xff), + (byte) ((dictSize >> 24) & 0xff), + }; + } + + @Override + Object getOptionsFromCoder(final Coder coder, final InputStream in) { + try { + final byte propsByte = coder.properties[0]; + int props = propsByte & 0xFF; + int pb = props / (9 * 5); + props -= pb * 9 * 5; + int lp = props / 9; + int lc = props - lp * 9; + LZMA2Options opts = new LZMA2Options(); + opts.setPb(pb); + opts.setLcLp(lc, lp); + opts.setDictSize(getDictionarySize(coder)); + return opts; + } catch (UnsupportedOptionsException ex) { + throw new RuntimeException(ex); + } + } + + private int getDictSize(final Object opts) { + if (opts instanceof LZMA2Options) { + return ((LZMA2Options) opts).getDictSize(); + } + return numberOptionOrDefault(opts); + } + + private int getDictionarySize(final Coder coder) throws IllegalArgumentException { + long dictSize = coder.properties[1]; + for (int i = 1; i < 4; i++) { + dictSize |= (coder.properties[i + 1] & 0xffl) << (8 * i); + } + return (int) dictSize; + } + + private LZMA2Options getOptions(final Object opts) { + if (opts instanceof LZMA2Options) { + return (LZMA2Options) opts; + } + final LZMA2Options options = new LZMA2Options(); + try { + options.setDictSize(numberOptionOrDefault(opts)); + } catch (UnsupportedOptionsException ex) { + throw new RuntimeException(ex); + } + return options; + } + + private int numberOptionOrDefault(final Object opts) { + return numberOptionOrDefault(opts, LZMA2Options.DICT_SIZE_DEFAULT); + } +} http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/site/xdoc/examples.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/examples.xml b/src/site/xdoc/examples.xml index a0b1059..c894d36 100644 --- a/src/site/xdoc/examples.xml +++ b/src/site/xdoc/examples.xml @@ -556,11 +556,11 @@ defIn.close(); <subsection name="7z"> - <p>Note that Commons Compress currently only supports - a subset of compression and encryption algorithms used for 7z - archives. For writing only uncompressed entries, - LZMA2, BZIP2 and Deflate are supported - reading also supports - LZMA and AES-256/SHA-256.</p> + <p>Note that Commons Compress currently only supports a subset + of compression and encryption algorithms used for 7z archives. + For writing only uncompressed entries, LZMA, LZMA2, BZIP2 and + Deflate are supported - reading also supports + AES-256/SHA-256.</p> <p>Multipart archives are not supported at all.</p> http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/site/xdoc/index.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 6e8b626..7280ccb 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -79,8 +79,9 @@ needed. This allows archives to be read from inputs and written to outputs that are seekable but are not represented by <code>File</code>s.</li> - <li>Added support for writing the legacy LZMA format - - this requires XZ for Java 1.6.</li> + <li>Added support for writing the legacy LZMA format as + compressor stream and inside 7z archives - this requires + XZ for Java 1.6.</li> </ul> </subsection> </section> http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/site/xdoc/limitations.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/limitations.xml b/src/site/xdoc/limitations.xml index bd60293..c5c2be4 100644 --- a/src/site/xdoc/limitations.xml +++ b/src/site/xdoc/limitations.xml @@ -38,9 +38,10 @@ archives, starting with 1.8 it will throw a <code>StreamingNotSupportedException</code> when reading from a 7z archive.</li> - <li>Encryption, solid compression, header compression and - LZMA (not LZMA2) are only supported when reading - archives</li> + <li>Encryption, solid compression and header compression and + are only supported when reading archives</li> + <li>Commons Compress 1.12 and earlier didn't support writing + LZMA.</li> <li>Several of the "methods" supported by 7z are not implemented in Compress.</li> <li>No support for multi-volume archives</li> http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java b/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java index 79e0019..d898be1 100644 --- a/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java +++ b/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java @@ -38,6 +38,11 @@ public class SevenZTestCase extends AbstractTestCase { } @Test + public void testSevenZArchiveCreationUsingLZMA() throws Exception { + testSevenZArchiveCreation(SevenZMethod.LZMA); + } + + @Test public void testSevenZArchiveCreationUsingLZMA2() throws Exception { testSevenZArchiveCreation(SevenZMethod.LZMA2); } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZMethodConfigurationTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZMethodConfigurationTest.java b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZMethodConfigurationTest.java index 1a08880..20fccbd 100644 --- a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZMethodConfigurationTest.java +++ b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZMethodConfigurationTest.java @@ -30,6 +30,19 @@ public class SevenZMethodConfigurationTest { } @Test + public void shouldAllowLZMA2OptionsForLZMA() { + Assert.assertNotNull(new SevenZMethodConfiguration(SevenZMethod.LZMA, + new LZMA2Options()) + .getOptions()); + } + + @Test + public void shouldAllowNumberForLZMA() { + Assert.assertNotNull(new SevenZMethodConfiguration(SevenZMethod.LZMA, 42) + .getOptions()); + } + + @Test public void shouldAllowLZMA2OptionsForLZMA2() { Assert.assertNotNull(new SevenZMethodConfiguration(SevenZMethod.LZMA2, new LZMA2Options()) @@ -55,6 +68,11 @@ public class SevenZMethodConfigurationTest { } @Test(expected = IllegalArgumentException.class) + public void shouldNotAllowStringOptionsForLZMA() { + new SevenZMethodConfiguration(SevenZMethod.LZMA, ""); + } + + @Test(expected = IllegalArgumentException.class) public void shouldNotAllowStringOptionsForLZMA2() { new SevenZMethodConfiguration(SevenZMethod.LZMA2, ""); } http://git-wip-us.apache.org/repos/asf/commons-compress/blob/9238eab6/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 aee1e02..feb988a 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 @@ -328,6 +328,22 @@ public class SevenZOutputFileTest extends AbstractTestCase { } @Test + public void testLzmaWithIntConfiguration() throws Exception { + output = new File(dir, "lzma-options.7z"); + // 1 MB dictionary + createAndReadBack(output, Collections + .singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA, 1 << 20))); + } + + @Test + public void testLzmaWithOptionsConfiguration() throws Exception { + output = new File(dir, "lzma-options2.7z"); + final LZMA2Options opts = new LZMA2Options(1); + createAndReadBack(output, Collections + .singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA, opts))); + } + + @Test public void testLzma2WithIntConfiguration() throws Exception { output = new File(dir, "lzma2-options.7z"); // 1 MB dictionary
