This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-compress.git
commit 44ae1f7ee4ea295abf692b6984513fda36dcc7a6 Author: Gary Gregory <[email protected]> AuthorDate: Sun Aug 9 07:48:17 2026 -0400 Sort members --- .../bzip2/BZip2CompressorInputStream.java | 18 +-- .../commons/compress/huffman/HuffmanDecoder.java | 148 ++++++++++----------- .../bzip2/BZip2CompressorInputStreamTest.java | 20 +-- .../Deflate64CompressorInputStreamTest.java | 26 ++-- .../compress/huffman/HuffmanDecoderTest.java | 88 ++++++------ 5 files changed, 150 insertions(+), 150 deletions(-) diff --git a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java index 772cd3761..08be86dd9 100644 --- a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java +++ b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java @@ -161,6 +161,11 @@ private static void checkBounds(final int checkVal, final int limitExclusive, fi } } + private static HuffmanDecoder getHuffmanDecoder(final Data dataShadow, final int zt) throws IOException { + checkBounds(zt, dataShadow.huffmanDecodersCount, "zt"); + return dataShadow.huffmanDecoders[zt]; + } + private static void makeMaps(final Data data) throws IOException { final boolean[] inUse = data.inUse; final byte[] seqToUnseq = data.seqToUnseq; @@ -175,7 +180,6 @@ private static void makeMaps(final Data data) throws IOException { data.inUseCount = nInUseShadow; } - /** * Checks if the signature matches what is expected for a bzip2 file. * @@ -187,6 +191,9 @@ private static void makeMaps(final Data data) throws IOException { public static boolean matches(final byte[] signature, final int length) { return length >= 3 && signature[0] == 'B' && signature[1] == 'Z' && signature[2] == 'h'; } + + // Variables used by setup* methods exclusively + static void recvDecodingTables(final BitInputStream bin, final Data dataShadow) throws IOException { final boolean[] inUse = dataShadow.inUse; final byte[] pos = dataShadow.recvDecodingTables_pos; @@ -278,8 +285,6 @@ static void recvDecodingTables(final BitInputStream bin, final Data dataShadow) dataShadow.huffmanDecodersCount = nGroups; } - // Variables used by setup* methods exclusively - /** * Index of the last char in the block, so the block size == last + 1. */ @@ -289,7 +294,6 @@ static void recvDecodingTables(final BitInputStream bin, final Data dataShadow) * Index in zptr[] of original string after sorting. */ private int origPtr; - /** * always: in the range 0 .. 9. The current block size is 100000 * this number. */ @@ -307,6 +311,7 @@ static void recvDecodingTables(final BitInputStream bin, final Data dataShadow) private int su_chPrev; private int su_i2; private int su_j2; + private int su_rNToGo; private int su_rTPos; @@ -791,9 +796,4 @@ private int setupRandPartC() throws IOException { this.su_count = 0; return setupRandPartA(); } - - private static HuffmanDecoder getHuffmanDecoder(final Data dataShadow, final int zt) throws IOException { - checkBounds(zt, dataShadow.huffmanDecodersCount, "zt"); - return dataShadow.huffmanDecoders[zt]; - } } diff --git a/src/main/java/org/apache/commons/compress/huffman/HuffmanDecoder.java b/src/main/java/org/apache/commons/compress/huffman/HuffmanDecoder.java index 71b336f02..718ab6c2b 100644 --- a/src/main/java/org/apache/commons/compress/huffman/HuffmanDecoder.java +++ b/src/main/java/org/apache/commons/compress/huffman/HuffmanDecoder.java @@ -55,6 +55,66 @@ public final class HuffmanDecoder { */ private static final int MAX_SUPPORTED_CODE_LENGTH = 30; + /** + * Builds canonical decode tables. + */ + private static void fillCodeTable(final int[] codeLengths, final int minLen, final int maxLen, final int[] bias, + final int[] limit, final int[] sorted) { + // 1) Histogram of code lengths + final int[] count = new int[maxLen + 1]; + for (int symbol = 0; symbol < codeLengths.length; symbol++) { + final int len = codeLengths[symbol]; + if (len == 0) { + continue; + } + count[codeLengths[symbol]]++; + } + // 2) Generate starting offsets into sorted symbol table + // The offsets are biased by -1 to simplify code in the next step + final int[] offset = new int[maxLen + 1]; + offset[0] = -1; + for (int len = 1; len <= maxLen; len++) { + offset[len] = offset[len - 1] + count[len - 1]; + } + // 3) Build table of symbols sorted by length, then by symbol + // Adjust offsets to point to the last element of each length + for (int symbol = 0; symbol < codeLengths.length; symbol++) { + final int len = codeLengths[symbol]; + if (len == 0) { + continue; + } + sorted[++offset[len]] = symbol; + } + // 4) Compute the largest left-justified code for each length + int firstCode = 0; + for (int len = minLen; len <= maxLen; len++) { + firstCode += count[len]; + limit[len] = firstCode - 1; + firstCode <<= 1; // prepare for next length + } + // 5) Compute the bias for each length + for (int len = minLen; len <= maxLen; len++) { + bias[len] = limit[len] - offset[len]; + } + } + + private static int readBit(final BitInputStream in) throws IOException { + final int bit = in.readBit(); + if (bit < 0) { + throw new EOFException("Truncated Huffman bit stream"); + } + return bit; + } + + private static int readBitsFully(final BitInputStream in, final int numBits) throws IOException { + final int code = (int) in.readBits(numBits); + if (code < 0) { + throw new EOFException("Truncated Huffman bit stream"); + } + // Adjust for bit order + return in.getByteOrder() == ByteOrder.BIG_ENDIAN ? code : Integer.reverse(code) >>> 32 - numBits; + } + /** Minimum non-zero code length */ private final int minLength; @@ -142,67 +202,6 @@ public HuffmanDecoder(final int[] codeLengths, final int minCodeLength, final in fillCodeTable(codeLengths, minLength, max, bias, limit, sorted); } - /** - * Gets the minimum code length (in bits) for this code set. - * - * @return minimum code length (in bits). - */ - public int getMinLength() { - return minLength; - } - - /** - * Gets the maximum code length (in bits) for this code set. - * - * @return maximum code length (in bits). - */ - public int getMaxLength() { - return maxLength; - } - - /** - * Builds canonical decode tables. - */ - private static void fillCodeTable(final int[] codeLengths, final int minLen, final int maxLen, final int[] bias, - final int[] limit, final int[] sorted) { - // 1) Histogram of code lengths - final int[] count = new int[maxLen + 1]; - for (int symbol = 0; symbol < codeLengths.length; symbol++) { - final int len = codeLengths[symbol]; - if (len == 0) { - continue; - } - count[codeLengths[symbol]]++; - } - // 2) Generate starting offsets into sorted symbol table - // The offsets are biased by -1 to simplify code in the next step - final int[] offset = new int[maxLen + 1]; - offset[0] = -1; - for (int len = 1; len <= maxLen; len++) { - offset[len] = offset[len - 1] + count[len - 1]; - } - // 3) Build table of symbols sorted by length, then by symbol - // Adjust offsets to point to the last element of each length - for (int symbol = 0; symbol < codeLengths.length; symbol++) { - final int len = codeLengths[symbol]; - if (len == 0) { - continue; - } - sorted[++offset[len]] = symbol; - } - // 4) Compute the largest left-justified code for each length - int firstCode = 0; - for (int len = minLen; len <= maxLen; len++) { - firstCode += count[len]; - limit[len] = firstCode - 1; - firstCode <<= 1; // prepare for next length - } - // 5) Compute the bias for each length - for (int len = minLen; len <= maxLen; len++) { - bias[len] = limit[len] - offset[len]; - } - } - /** * Decodes one symbol from the input bitstream. * @@ -225,20 +224,21 @@ public int decodeSymbol(final BitInputStream in) throws IOException { return sorted[code - bias[len]]; } - private static int readBit(final BitInputStream in) throws IOException { - final int bit = in.readBit(); - if (bit < 0) { - throw new EOFException("Truncated Huffman bit stream"); - } - return bit; + /** + * Gets the maximum code length (in bits) for this code set. + * + * @return maximum code length (in bits). + */ + public int getMaxLength() { + return maxLength; } - private static int readBitsFully(final BitInputStream in, final int numBits) throws IOException { - final int code = (int) in.readBits(numBits); - if (code < 0) { - throw new EOFException("Truncated Huffman bit stream"); - } - // Adjust for bit order - return in.getByteOrder() == ByteOrder.BIG_ENDIAN ? code : Integer.reverse(code) >>> 32 - numBits; + /** + * Gets the minimum code length (in bits) for this code set. + * + * @return minimum code length (in bits). + */ + public int getMinLength() { + return minLength; } } diff --git a/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java index 71417f1c2..515f3edb6 100644 --- a/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java +++ b/src/test/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStreamTest.java @@ -103,6 +103,16 @@ private BitInputStream prepareDecodingTables(final int codeLength) { return new BitInputStream(new ByteArrayInputStream(stream.toByteArray()), ByteOrder.BIG_ENDIAN); } + @Test + void testDecompress() throws Exception { + try (InputStream is = newInputStream("lorem-ipsum.txt.bz2"); + BZip2CompressorInputStream in = new BZip2CompressorInputStream(is)) { + final byte[] data = IOUtils.toByteArray(in); + assertEquals(144060, data.length); + assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a", DigestUtils.sha256Hex(data)); + } + } + @Test void testFinishClose() throws Exception { // Create a big random piece of data @@ -262,14 +272,4 @@ void testSingleByteReadConsistentlyReturnsMinusOneAtEof() throws IOException { } } - @Test - void testDecompress() throws Exception { - try (InputStream is = newInputStream("lorem-ipsum.txt.bz2"); - BZip2CompressorInputStream in = new BZip2CompressorInputStream(is)) { - final byte[] data = IOUtils.toByteArray(in); - assertEquals(144060, data.length); - assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a", DigestUtils.sha256Hex(data)); - } - } - } diff --git a/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java index 806689f71..5fa42992d 100644 --- a/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java +++ b/src/test/java/org/apache/commons/compress/compressors/deflate64/Deflate64CompressorInputStreamTest.java @@ -86,6 +86,19 @@ void testCloseIsDelegatedJustOnce() throws Exception { Mockito.verify(decoder, times(1)).close(); } + @Test + void testDecompress() throws Exception { + try (ZipArchiveInputStream archive = new ZipArchiveInputStream(AbstractTest.newInputStream("lorem-ipsum-deflate64.zip"))) { + final ZipArchiveEntry entry = archive.getNextEntry(); + assertEquals("lorem-ipsum.txt", entry.getName()); + assertEquals(ZipMethod.ENHANCED_DEFLATED, ZipMethod.getMethodByCode(entry.getMethod())); + + final byte[] data = IOUtils.toByteArray(archive); + assertEquals(144060, data.length); + assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a", DigestUtils.sha256Hex(data)); + } + } + @Test void testDelegatesAvailable() throws Exception { Mockito.when(decoder.available()).thenReturn(1024); @@ -247,17 +260,4 @@ void testUncompressedBlockViaFactory() throws Exception { assertNull(br.readLine()); } } - - @Test - void testDecompress() throws Exception { - try (ZipArchiveInputStream archive = new ZipArchiveInputStream(AbstractTest.newInputStream("lorem-ipsum-deflate64.zip"))) { - final ZipArchiveEntry entry = archive.getNextEntry(); - assertEquals("lorem-ipsum.txt", entry.getName()); - assertEquals(ZipMethod.ENHANCED_DEFLATED, ZipMethod.getMethodByCode(entry.getMethod())); - - final byte[] data = IOUtils.toByteArray(archive); - assertEquals(144060, data.length); - assertEquals("a00c4f3f36515c96b2faef71c054e7f3e86a4f0f4ed4824cb7c5293bb455d28a", DigestUtils.sha256Hex(data)); - } - } } diff --git a/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java b/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java index 40a5b561d..6cc56a816 100644 --- a/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java +++ b/src/test/java/org/apache/commons/compress/huffman/HuffmanDecoderTest.java @@ -42,20 +42,6 @@ class HuffmanDecoderTest { - @Test - void testCreateHuffmanDecodingTablesWithLargeAlphaSize() { - // Use a codeLengths array with length equal to MAX_ALPHA_SIZE (258) to test array bounds. - final int[] codeLengths = new int[258]; - for (int i = 0; i < codeLengths.length; i++) { - // Use all code lengths within valid range [1, 20] - codeLengths[i] = (char) (i % 20 + 1); - } - final HuffmanDecoder decoder = assertDoesNotThrow(() -> new HuffmanDecoder(codeLengths, 1, 20), - "HuffmanDecoder constructor should not throw for valid codeLengths array of MAX_ALPHA_SIZE"); - assertEquals(decoder.getMinLength(), 1, "Minimum code length should be 1"); - assertEquals(decoder.getMaxLength(), 20, "Maximum code length should be 20"); - } - static Stream<Arguments> testDecodeSymbols() { // @formatter:off return Stream.of( @@ -120,6 +106,26 @@ static Stream<Arguments> testDecodeSymbols() { // @formatter:on } + private int decodeSymbol(HuffmanDecoder decoder, final byte... data) throws IOException { + try (BitInputStream in = new BitInputStream(new ByteArrayInputStream(data), ByteOrder.BIG_ENDIAN)) { + return decoder.decodeSymbol(in); + } + } + + @Test + void testCreateHuffmanDecodingTablesWithLargeAlphaSize() { + // Use a codeLengths array with length equal to MAX_ALPHA_SIZE (258) to test array bounds. + final int[] codeLengths = new int[258]; + for (int i = 0; i < codeLengths.length; i++) { + // Use all code lengths within valid range [1, 20] + codeLengths[i] = (char) (i % 20 + 1); + } + final HuffmanDecoder decoder = assertDoesNotThrow(() -> new HuffmanDecoder(codeLengths, 1, 20), + "HuffmanDecoder constructor should not throw for valid codeLengths array of MAX_ALPHA_SIZE"); + assertEquals(decoder.getMinLength(), 1, "Minimum code length should be 1"); + assertEquals(decoder.getMaxLength(), 20, "Maximum code length should be 20"); + } + @ParameterizedTest @MethodSource void testDecodeSymbols(final int[] codeLengths, final byte[] inputData, final List<Integer> expectedSymbols, final ByteOrder byteOrder) throws IOException { @@ -133,32 +139,6 @@ void testDecodeSymbols(final int[] codeLengths, final byte[] inputData, final Li assertEquals(expectedSymbols, actualSymbols, "Decoded symbols do not match expected symbols"); } - @Test - void testNoCodeLengths() throws Exception { - final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new HuffmanDecoder(new int[0]), - "Expected IllegalArgumentException for empty code length list"); - assertEquals("codeLengthSize must be > 0; was 0", e.getMessage()); - } - - @Test - void testSingleCodeLength() throws Exception { - final int[] length = { 1 }; - // Value: 0 - final HuffmanDecoder decoder = new HuffmanDecoder(length); - assertEquals(0, decodeSymbol(decoder, (byte) 0x00)); // 0xxx xxxx - final CompressorException e = assertThrows(CompressorException.class, () -> decodeSymbol(decoder, (byte) 0x80), - "Expected CompressorException for invalid bitstream"); - assertEquals("Invalid Huffman code: 2", e.getMessage()); - } - - @Test - void testNoLeafNodes() throws Exception { - final HuffmanDecoder decoder = new HuffmanDecoder(new int[] { 0, 0, 0, 0, 0 }); - final CompressorException e = assertThrows(CompressorException.class, () -> decodeSymbol(decoder, (byte) 0, (byte) 0, (byte) 0, (byte) 0), - "Expected CompressorException when decoding symbols for tree with no leaf nodes"); - assertEquals("Invalid Huffman code: 0", e.getMessage()); - } - @Test void testInvalidBitstream() throws Exception { final int[] length = { 4, 2, 3, 0, 5, 0, 1 }; @@ -174,6 +154,21 @@ void testInvalidBitstream() throws Exception { assertEquals("Invalid Huffman code: 62", e.getMessage()); } + @Test + void testNoCodeLengths() throws Exception { + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new HuffmanDecoder(new int[0]), + "Expected IllegalArgumentException for empty code length list"); + assertEquals("codeLengthSize must be > 0; was 0", e.getMessage()); + } + + @Test + void testNoLeafNodes() throws Exception { + final HuffmanDecoder decoder = new HuffmanDecoder(new int[] { 0, 0, 0, 0, 0 }); + final CompressorException e = assertThrows(CompressorException.class, () -> decodeSymbol(decoder, (byte) 0, (byte) 0, (byte) 0, (byte) 0), + "Expected CompressorException when decoding symbols for tree with no leaf nodes"); + assertEquals("Invalid Huffman code: 0", e.getMessage()); + } + @Test void testReadEof() throws Exception { final int[] length = { 4, 2, 3, 0, 5, 5, 1 }; @@ -188,9 +183,14 @@ void testReadEof() throws Exception { } } - private int decodeSymbol(HuffmanDecoder decoder, final byte... data) throws IOException { - try (BitInputStream in = new BitInputStream(new ByteArrayInputStream(data), ByteOrder.BIG_ENDIAN)) { - return decoder.decodeSymbol(in); - } + @Test + void testSingleCodeLength() throws Exception { + final int[] length = { 1 }; + // Value: 0 + final HuffmanDecoder decoder = new HuffmanDecoder(length); + assertEquals(0, decodeSymbol(decoder, (byte) 0x00)); // 0xxx xxxx + final CompressorException e = assertThrows(CompressorException.class, () -> decodeSymbol(decoder, (byte) 0x80), + "Expected CompressorException for invalid bitstream"); + assertEquals("Invalid Huffman code: 2", e.getMessage()); } }
