Repository: commons-compress Updated Branches: refs/heads/master a793612b9 -> 22fe7f3c8
[COMPRESS-392] Add Brotli decoder based on the Google Brotli library. Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/22fe7f3c Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/22fe7f3c Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/22fe7f3c Branch: refs/heads/master Commit: 22fe7f3c8b3fa204c2406c3d15811b2581e1b7b5 Parents: a793612 Author: Philippe Mouawad <p.moua...@ubik-ingenierie.com> Authored: Tue May 2 12:23:55 2017 -0700 Committer: Gary Gregory <garydgreg...@gmail.com> Committed: Tue May 2 12:23:55 2017 -0700 ---------------------------------------------------------------------- .../brotli/BrotliCompressorInputStream.java | 151 +++++++++++++++++++ .../compressors/brotli/BrotliUtils.java | 88 +++++++++++ .../brotli/BrotliCompressorInputStreamTest.java | 133 ++++++++++++++++ src/test/resources/brotli.testdata.compressed | Bin 0 -> 12 bytes src/test/resources/brotli.testdata.uncompressed | 1 + 5 files changed, 373 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/22fe7f3c/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStream.java new file mode 100644 index 0000000..26b56d9 --- /dev/null +++ b/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStream.java @@ -0,0 +1,151 @@ +/* + * 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.brotli; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.compress.compressors.CompressorInputStream; + +/** + * {@link FilterInputStream} implementation to decode Brotli encoded stream. + * Library relies on <a href="https://github.com/google/brotli">Google brotli</a> + * + * @since 1.14 + */ +public class BrotliCompressorInputStream extends CompressorInputStream { + + private org.brotli.dec.BrotliInputStream decIS; + + public BrotliCompressorInputStream(InputStream in) throws IOException { + this.decIS = new org.brotli.dec.BrotliInputStream(in); + } + + /** + * @return + * @throws IOException + * @see java.io.InputStream#available() + */ + public int available() throws IOException { + return decIS.available(); + } + + /** + * @throws IOException + * @see org.brotli.dec.BrotliInputStream#close() + */ + public void close() throws IOException { + decIS.close(); + } + + /** + * @return + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return decIS.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public int read(byte[] b) throws IOException { + return decIS.read(b); + } + + /** + * @param obj + * @return + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + return decIS.equals(obj); + } + + /** + * @param n + * @return + * @throws IOException + * @see java.io.InputStream#skip(long) + */ + public long skip(long n) throws IOException { + return decIS.skip(n); + } + + /** + * @param readlimit + * @see java.io.InputStream#mark(int) + */ + public void mark(int readlimit) { + decIS.mark(readlimit); + } + + /** + * @return + * @see java.io.InputStream#markSupported() + */ + public boolean markSupported() { + return decIS.markSupported(); + } + + /** {@inheritDoc} */ + @Override + public int read() throws IOException { + final int ret = decIS.read(); + count(ret == -1 ? 0 : 1); + return ret; + } + + /** {@inheritDoc} */ + @Override + public int read(byte[] buf, int off, int len) throws IOException { + final int ret = decIS.read(buf, off, len); + count(ret); + return ret; + } + + /** + * @return + * @see java.lang.Object#toString() + */ + public String toString() { + return decIS.toString(); + } + + /** + * @throws IOException + * @see java.io.InputStream#reset() + */ + public void reset() throws IOException { + decIS.reset(); + } + + + /** + * There is no magic for Brotli + * + * @param signature + * the bytes to check + * @param length + * the number of bytes to check + * @return true + */ + static boolean matches(final byte[] signature, final int length) { + return true; + } +} http://git-wip-us.apache.org/repos/asf/commons-compress/blob/22fe7f3c/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliUtils.java b/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliUtils.java new file mode 100644 index 0000000..1f7ebca --- /dev/null +++ b/src/main/java/org/apache/commons/compress/compressors/brotli/BrotliUtils.java @@ -0,0 +1,88 @@ +/* + * 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.brotli; + +/** + * Utility code for the Brotli compression format. + * @ThreadSafe + * @since 1.14 + */ +public class BrotliUtils { + + static enum CachedAvailability { + DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE + } + + private static volatile CachedAvailability cachedBrotliAvailability; + + static { + cachedBrotliAvailability = CachedAvailability.DONT_CACHE; + try { + Class.forName("org.osgi.framework.BundleEvent"); + } catch (final Exception ex) { + setCacheBrotliAvailablity(true); + } + } + + /** Private constructor to prevent instantiation of this utility class. */ + private BrotliUtils() { + } + + + /** + * Are the classes required to support Brotli compression available? + * @return true if the classes required to support Brotli compression are available + */ + public static boolean isBrotliCompressionAvailable() { + final CachedAvailability cachedResult = cachedBrotliAvailability; + if (cachedResult != CachedAvailability.DONT_CACHE) { + return cachedResult == CachedAvailability.CACHED_AVAILABLE; + } + return internalIsBrotliCompressionAvailable(); + } + + private static boolean internalIsBrotliCompressionAvailable() { + try { + return BrotliCompressorInputStream.matches(null, 0); + } catch (final NoClassDefFoundError error) { + return false; + } + } + + /** + * Whether to cache the result of the Brotli for Java 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 setCacheBrotliAvailablity(final boolean doCache) { + if (!doCache) { + cachedBrotliAvailability = CachedAvailability.DONT_CACHE; + } else if (cachedBrotliAvailability == CachedAvailability.DONT_CACHE) { + final boolean hasXz = internalIsBrotliCompressionAvailable(); + cachedBrotliAvailability = hasXz ? CachedAvailability.CACHED_AVAILABLE + : CachedAvailability.CACHED_UNAVAILABLE; + } + } + + // only exists to support unit tests + static CachedAvailability getCachedBrotliAvailability() { + return cachedBrotliAvailability; + } +} http://git-wip-us.apache.org/repos/asf/commons-compress/blob/22fe7f3c/src/test/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStreamTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStreamTest.java new file mode 100644 index 0000000..3aca075 --- /dev/null +++ b/src/test/java/org/apache/commons/compress/compressors/brotli/BrotliCompressorInputStreamTest.java @@ -0,0 +1,133 @@ +/* + * 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.brotli; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.compress.AbstractTestCase; +import org.apache.commons.compress.utils.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +public class BrotliCompressorInputStreamTest { + + /** + * Test bridge works fine + * @throws {@link IOException} + */ + @Test + public void testBrotliDecode() throws IOException { + final File input = AbstractTestCase.getFile("brotli.testdata.compressed"); + final File expected = AbstractTestCase.getFile("brotli.testdata.uncompressed"); + try (InputStream inputStream = new FileInputStream(input); + InputStream expectedStream = new FileInputStream(expected); + BrotliCompressorInputStream brotliInputStream = new BrotliCompressorInputStream(inputStream)) { + final byte[] b = new byte[20]; + IOUtils.readFully(expectedStream, b); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int readByte = -1; + while((readByte = brotliInputStream.read()) != -1) { + bos.write(readByte); + } + Assert.assertArrayEquals(b, bos.toByteArray()); + } + } + + @Test + public void testCachingIsEnabledByDefaultAndXZIsPresent() { + assertEquals(BrotliUtils.CachedAvailability.CACHED_AVAILABLE, BrotliUtils.getCachedBrotliAvailability()); + assertTrue(BrotliUtils.isBrotliCompressionAvailable()); + } + + @Test + public void testCanTurnOffCaching() { + try { + BrotliUtils.setCacheBrotliAvailablity(false); + assertEquals(BrotliUtils.CachedAvailability.DONT_CACHE, BrotliUtils.getCachedBrotliAvailability()); + assertTrue(BrotliUtils.isBrotliCompressionAvailable()); + } finally { + BrotliUtils.setCacheBrotliAvailablity(true); + } + } + + @Test + public void testTurningOnCachingReEvaluatesAvailability() { + try { + BrotliUtils.setCacheBrotliAvailablity(false); + assertEquals(BrotliUtils.CachedAvailability.DONT_CACHE, BrotliUtils.getCachedBrotliAvailability()); + BrotliUtils.setCacheBrotliAvailablity(true); + assertEquals(BrotliUtils.CachedAvailability.CACHED_AVAILABLE, BrotliUtils.getCachedBrotliAvailability()); + } finally { + BrotliUtils.setCacheBrotliAvailablity(true); + } + } + + + @Test + public void availableShouldReturnZero() throws IOException { + final File input = AbstractTestCase.getFile("brotli.testdata.compressed"); + try (InputStream is = new FileInputStream(input)) { + final BrotliCompressorInputStream in = + new BrotliCompressorInputStream(is); + Assert.assertTrue(in.available() == 0); + in.close(); + } + } + + @Test + public void shouldBeAbleToSkipAByte() throws IOException { + final File input = AbstractTestCase.getFile("brotli.testdata.compressed"); + try (InputStream is = new FileInputStream(input)) { + final BrotliCompressorInputStream in = + new BrotliCompressorInputStream(is); + Assert.assertEquals(1, in.skip(1)); + in.close(); + } + } + + @Test + public void singleByteReadWorksAsExpected() throws IOException { + final File input = AbstractTestCase.getFile("brotli.testdata.compressed"); + try (InputStream is = new FileInputStream(input)) { + final BrotliCompressorInputStream in = + new BrotliCompressorInputStream(is); + // starts with filename "XXX" + Assert.assertEquals('X', in.read()); + in.close(); + } + } + + @Test + public void singleByteReadReturnsMinusOneAtEof() throws IOException { + final File input = AbstractTestCase.getFile("brotli.testdata.compressed"); + try (InputStream is = new FileInputStream(input)) { + final BrotliCompressorInputStream in = + new BrotliCompressorInputStream(is); + IOUtils.toByteArray(in); + Assert.assertEquals(-1, in.read()); + in.close(); + } + } +} http://git-wip-us.apache.org/repos/asf/commons-compress/blob/22fe7f3c/src/test/resources/brotli.testdata.compressed ---------------------------------------------------------------------- diff --git a/src/test/resources/brotli.testdata.compressed b/src/test/resources/brotli.testdata.compressed new file mode 100644 index 0000000..3769516 Binary files /dev/null and b/src/test/resources/brotli.testdata.compressed differ http://git-wip-us.apache.org/repos/asf/commons-compress/blob/22fe7f3c/src/test/resources/brotli.testdata.uncompressed ---------------------------------------------------------------------- diff --git a/src/test/resources/brotli.testdata.uncompressed b/src/test/resources/brotli.testdata.uncompressed new file mode 100644 index 0000000..3f9cf86 --- /dev/null +++ b/src/test/resources/brotli.testdata.uncompressed @@ -0,0 +1 @@ +XXXXXXXXXXYYYYYYYYYY \ No newline at end of file