COMPRESS-313 add auto-detection for LZMA streams
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/compress/trunk@1670129 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 5e2bd81..f3ae88a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -54,6 +54,9 @@
This also changes the superclass of ZCompressorInputStream.
">
+ <action issue="COMPRESS-313" type="add" date="2015-03-30">
+ CompressorStreamFactory can now auto-detect LZMA streams.
+ </action>
<action issue="COMPRESS-312" type="fix" date="2015-03-28">
TarArchiveEntry's constructor with a File and a String arg
didn't normalize the name.
diff --git a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
index 4bc1810..adca670 100644
--- a/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
+++ b/src/main/java/org/apache/commons/compress/compressors/CompressorStreamFactory.java
@@ -29,6 +29,7 @@
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
+import org.apache.commons.compress.compressors.lzma.LZMAUtils;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.apache.commons.compress.compressors.xz.XZUtils;
@@ -241,6 +242,11 @@
return new XZCompressorInputStream(in, decompressConcatenated);
}
+ if (LZMAUtils.matches(signature, signatureLength) &&
+ LZMAUtils.isLZMACompressionAvailable()) {
+ return new LZMACompressorInputStream(in);
+ }
+
} catch (IOException e) {
throw new CompressorException("Failed to detect Compressor from InputStream.", e);
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
index 142b617..f9a8eae 100644
--- a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
+++ b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMACompressorInputStream.java
@@ -81,4 +81,36 @@
public void close() throws IOException {
in.close();
}
+
+ /**
+ * Checks if the signature matches what is expected for an lzma file.
+ *
+ * @param signature
+ * the bytes to check
+ * @param length
+ * the number of bytes to check
+ * @return true, if this stream is an lzma compressed stream, false otherwise
+ *
+ * @since 1.10
+ */
+ public static boolean matches(byte[] signature, int length) {
+
+ if (signature == null || length < 3) {
+ return false;
+ }
+
+ if (signature[0] != 0x5d) {
+ return false;
+ }
+
+ if (signature[1] != 0) {
+ return false;
+ }
+
+ if (signature[2] != 0) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/src/main/java/org/apache/commons/compress/compressors/lzma/LZMAUtils.java b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMAUtils.java
new file mode 100644
index 0000000..7d3e1be
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/compressors/lzma/LZMAUtils.java
@@ -0,0 +1,161 @@
+/*
+ * 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.compressors.lzma;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.compress.compressors.FileNameUtil;
+
+/**
+ * Utility code for the lzma compression format.
+ * @ThreadSafe
+ * @since 1.10
+ */
+public class LZMAUtils {
+
+ private static final FileNameUtil fileNameUtil;
+
+ /**
+ * LZMA Header Magic Bytes begin a LZMA file.
+ */
+ private static final byte[] HEADER_MAGIC = {
+ (byte) 0x5D, 0, 0
+ };
+
+ static enum CachedAvailability {
+ DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
+ }
+
+ private static volatile CachedAvailability cachedLZMAAvailability;
+
+ static {
+ Map<String, String> uncompressSuffix = new HashMap<String, String>();
+ uncompressSuffix.put(".lzma", "");
+ uncompressSuffix.put("-lzma", "");
+ fileNameUtil = new FileNameUtil(uncompressSuffix, ".lzma");
+ cachedLZMAAvailability = CachedAvailability.DONT_CACHE;
+ try {
+ Class.forName("org.osgi.framework.BundleEvent");
+ } catch (Exception ex) {
+ setCacheLZMAAvailablity(true);
+ }
+ }
+
+ /** Private constructor to prevent instantiation of this utility class. */
+ private LZMAUtils() {
+ }
+
+ /**
+ * Checks if the signature matches what is expected for a .lzma file.
+ *
+ * @param signature the bytes to check
+ * @param length the number of bytes to check
+ * @return true if signature matches the .lzma magic bytes, false otherwise
+ */
+ public static boolean matches(byte[] signature, int length) {
+ if (length < HEADER_MAGIC.length) {
+ return false;
+ }
+
+ for (int i = 0; i < HEADER_MAGIC.length; ++i) {
+ if (signature[i] != HEADER_MAGIC[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Are the classes required to support LZMA compression available?
+ */
+ public static boolean isLZMACompressionAvailable() {
+ final CachedAvailability cachedResult = cachedLZMAAvailability;
+ if (cachedResult != CachedAvailability.DONT_CACHE) {
+ return cachedResult == CachedAvailability.CACHED_AVAILABLE;
+ }
+ return internalIsLZMACompressionAvailable();
+ }
+
+ private static boolean internalIsLZMACompressionAvailable() {
+ try {
+ LZMACompressorInputStream.matches(null, 0);
+ return true;
+ } catch (NoClassDefFoundError error) {
+ return false;
+ }
+ }
+
+ /**
+ * Detects common lzma suffixes in the given filename.
+ *
+ * @param filename name of a file
+ * @return {@code true} if the filename has a common lzma suffix,
+ * {@code false} otherwise
+ */
+ public static boolean isCompressedFilename(String filename) {
+ return fileNameUtil.isCompressedFilename(filename);
+ }
+
+ /**
+ * Maps the given name of a lzma-compressed file to the name that
+ * the file should have after uncompression. Any filenames with
+ * the generic ".lzma" suffix (or any other generic lzma suffix)
+ * is mapped to a name without that suffix. If no lzma suffix is
+ * detected, then the filename is returned unmapped.
+ *
+ * @param filename name of a file
+ * @return name of the corresponding uncompressed file
+ */
+ public static String getUncompressedFilename(String filename) {
+ return fileNameUtil.getUncompressedFilename(filename);
+ }
+
+ /**
+ * Maps the given filename to the name that the file should have after
+ * compression with lzma.
+ *
+ * @param filename name of a file
+ * @return name of the corresponding compressed file
+ */
+ public static String getCompressedFilename(String filename) {
+ return fileNameUtil.getCompressedFilename(filename);
+ }
+
+ /**
+ * Whether to cache the result of the LZMA check.
+ *
+ * <p>This defaults to {@code false} in an OSGi environment and {@code true} otherwise.</p>
+ * @param doCache whether to cache the result
+ */
+ public static void setCacheLZMAAvailablity(boolean doCache) {
+ if (!doCache) {
+ cachedLZMAAvailability = CachedAvailability.DONT_CACHE;
+ } else if (cachedLZMAAvailability == CachedAvailability.DONT_CACHE) {
+ final boolean hasLzma = internalIsLZMACompressionAvailable();
+ cachedLZMAAvailability = hasLzma ? CachedAvailability.CACHED_AVAILABLE
+ : CachedAvailability.CACHED_UNAVAILABLE;
+ }
+ }
+
+ // only exists to support unit tests
+ static CachedAvailability getCachedLZMAAvailability() {
+ return cachedLZMAAvailability;
+ }
+}
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
index f738f76..0f095b0 100644
--- a/src/site/xdoc/index.xml
+++ b/src/site/xdoc/index.xml
@@ -68,6 +68,7 @@
<li>Added support for parallel ZIP compression.</li>
<li>Added support for raw transfer of entries from one ZIP file to another without uncompress/compress.</li>
<li>Performance improvements for creating ZIP files with lots of small entries.</li>
+ <li>Added auto-detection for LZMA.</li>
</ul>
</subsection>
</section>
diff --git a/src/test/java/org/apache/commons/compress/compressors/LZMATestCase.java b/src/test/java/org/apache/commons/compress/compressors/LZMATestCase.java
index cdc00f5..56b5c71 100644
--- a/src/test/java/org/apache/commons/compress/compressors/LZMATestCase.java
+++ b/src/test/java/org/apache/commons/compress/compressors/LZMATestCase.java
@@ -18,9 +18,11 @@
*/
package org.apache.commons.compress.compressors;
+import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.compress.AbstractTestCase;
@@ -37,18 +39,36 @@
final InputStream is = new FileInputStream(input);
try {
final CompressorInputStream in = new LZMACompressorInputStream(is);
- FileOutputStream out = null;
- try {
- out = new FileOutputStream(output);
- IOUtils.copy(in, out);
- } finally {
- if (out != null) {
- out.close();
- }
- in.close();
- }
+ copy(in, output);
} finally {
is.close();
}
}
+
+ @Test
+ public void testLZMAUnarchiveWithAutodetection() throws Exception {
+ final File input = getFile("bla.tar.lzma");
+ final File output = new File(dir, "bla.tar");
+ final InputStream is = new BufferedInputStream(new FileInputStream(input));
+ try {
+ final CompressorInputStream in = new CompressorStreamFactory()
+ .createCompressorInputStream(is);
+ copy(in, output);
+ } finally {
+ is.close();
+ }
+ }
+
+ private void copy(InputStream in, File output) throws IOException {
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(output);
+ IOUtils.copy(in, out);
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ in.close();
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/compress/compressors/lzma/LZMAUtilsTestCase.java b/src/test/java/org/apache/commons/compress/compressors/lzma/LZMAUtilsTestCase.java
new file mode 100644
index 0000000..357c9fb
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/compressors/lzma/LZMAUtilsTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * 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.compressors.lzma;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class LZMAUtilsTestCase {
+
+ @Test
+ public void testIsCompressedFilename() {
+ assertFalse(LZMAUtils.isCompressedFilename(""));
+ assertFalse(LZMAUtils.isCompressedFilename(".lzma"));
+
+ assertTrue(LZMAUtils.isCompressedFilename("x.lzma"));
+ assertTrue(LZMAUtils.isCompressedFilename("x-lzma"));
+
+ assertFalse(LZMAUtils.isCompressedFilename("xxgz"));
+ assertFalse(LZMAUtils.isCompressedFilename("lzmaz"));
+ assertFalse(LZMAUtils.isCompressedFilename("xaz"));
+
+ assertFalse(LZMAUtils.isCompressedFilename("x.lzma "));
+ assertFalse(LZMAUtils.isCompressedFilename("x.lzma\n"));
+ assertFalse(LZMAUtils.isCompressedFilename("x.lzma.y"));
+ }
+
+ @Test
+ public void testGetUncompressedFilename() {
+ assertEquals("", LZMAUtils.getUncompressedFilename(""));
+ assertEquals(".lzma", LZMAUtils.getUncompressedFilename(".lzma"));
+
+ assertEquals("x", LZMAUtils.getUncompressedFilename("x.lzma"));
+ assertEquals("x", LZMAUtils.getUncompressedFilename("x-lzma"));
+
+ assertEquals("x.lzma ", LZMAUtils.getUncompressedFilename("x.lzma "));
+ assertEquals("x.lzma\n", LZMAUtils.getUncompressedFilename("x.lzma\n"));
+ assertEquals("x.lzma.y", LZMAUtils.getUncompressedFilename("x.lzma.y"));
+ }
+
+ @Test
+ public void testGetCompressedFilename() {
+ assertEquals(".lzma", LZMAUtils.getCompressedFilename(""));
+ assertEquals("x.lzma", LZMAUtils.getCompressedFilename("x"));
+
+ assertEquals("x.wmf .lzma", LZMAUtils.getCompressedFilename("x.wmf "));
+ assertEquals("x.wmf\n.lzma", LZMAUtils.getCompressedFilename("x.wmf\n"));
+ assertEquals("x.wmf.y.lzma", LZMAUtils.getCompressedFilename("x.wmf.y"));
+ }
+
+ @Test
+ public void testMatches() {
+ byte[] data = {
+ (byte) 0x5D, 0, 0,
+ };
+ assertFalse(LZMAUtils.matches(data, 2));
+ assertTrue(LZMAUtils.matches(data, 3));
+ assertTrue(LZMAUtils.matches(data, 4));
+ data[2] = '0';
+ assertFalse(LZMAUtils.matches(data, 3));
+ }
+
+ @Test
+ public void testCachingIsEnabledByDefaultAndLZMAIsPresent() {
+ assertEquals(LZMAUtils.CachedAvailability.CACHED_AVAILABLE, LZMAUtils.getCachedLZMAAvailability());
+ assertTrue(LZMAUtils.isLZMACompressionAvailable());
+ }
+
+ @Test
+ public void testCanTurnOffCaching() {
+ try {
+ LZMAUtils.setCacheLZMAAvailablity(false);
+ assertEquals(LZMAUtils.CachedAvailability.DONT_CACHE, LZMAUtils.getCachedLZMAAvailability());
+ assertTrue(LZMAUtils.isLZMACompressionAvailable());
+ } finally {
+ LZMAUtils.setCacheLZMAAvailablity(true);
+ }
+ }
+
+ @Test
+ public void testTurningOnCachingReEvaluatesAvailability() {
+ try {
+ LZMAUtils.setCacheLZMAAvailablity(false);
+ assertEquals(LZMAUtils.CachedAvailability.DONT_CACHE, LZMAUtils.getCachedLZMAAvailability());
+ LZMAUtils.setCacheLZMAAvailablity(true);
+ assertEquals(LZMAUtils.CachedAvailability.CACHED_AVAILABLE, LZMAUtils.getCachedLZMAAvailability());
+ } finally {
+ LZMAUtils.setCacheLZMAAvailablity(true);
+ }
+ }
+
+}