UnshrinkingInputStream.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  * http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.compress.archivers.zip;

  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.nio.ByteOrder;

  23. import org.apache.commons.compress.compressors.lzw.LZWInputStream;

  24. /**
  25.  * Input stream that decompresses ZIP method 1 (unshrinking). A variation of the LZW algorithm, with some twists.
  26.  *
  27.  * @NotThreadSafe
  28.  * @since 1.7
  29.  */
  30. final class UnshrinkingInputStream extends LZWInputStream {
  31.     private static final int MAX_CODE_SIZE = 13;
  32.     private static final int MAX_TABLE_SIZE = 1 << MAX_CODE_SIZE;
  33.     private final boolean[] isUsed;

  34.     /**
  35.      * IOException is not actually thrown!
  36.      *
  37.      * @param inputStream
  38.      */
  39.     UnshrinkingInputStream(final InputStream inputStream) {
  40.         super(inputStream, ByteOrder.LITTLE_ENDIAN);
  41.         setClearCode(DEFAULT_CODE_SIZE);
  42.         initializeTables(MAX_CODE_SIZE);
  43.         isUsed = new boolean[getPrefixesLength()];
  44.         for (int i = 0; i < 1 << 8; i++) {
  45.             isUsed[i] = true;
  46.         }
  47.         setTableSize(getClearCode() + 1);
  48.     }

  49.     @Override
  50.     protected int addEntry(final int previousCode, final byte character) throws IOException {
  51.         int tableSize = getTableSize();
  52.         while (tableSize < MAX_TABLE_SIZE && isUsed[tableSize]) {
  53.             tableSize++;
  54.         }
  55.         setTableSize(tableSize);
  56.         final int idx = addEntry(previousCode, character, MAX_TABLE_SIZE);
  57.         if (idx >= 0) {
  58.             isUsed[idx] = true;
  59.         }
  60.         return idx;
  61.     }

  62.     @Override
  63.     protected int decompressNextSymbol() throws IOException {
  64.         //
  65.         // table entry table entry
  66.         // _____________ _____
  67.         // table entry / \ / \
  68.         // ____________/ \ \
  69.         // / / \ / \ \
  70.         // +---+---+---+---+---+---+---+---+---+---+
  71.         // | . | . | . | . | . | . | . | . | . | . |
  72.         // +---+---+---+---+---+---+---+---+---+---+
  73.         // |<--------->|<------------->|<----->|<->|
  74.         // symbol symbol symbol symbol
  75.         //
  76.         final int code = readNextCode();
  77.         if (code < 0) {
  78.             return -1;
  79.         }
  80.         if (code != getClearCode()) {
  81.             boolean addedUnfinishedEntry = false;
  82.             int effectiveCode = code;
  83.             if (!isUsed[code]) {
  84.                 effectiveCode = addRepeatOfPreviousCode();
  85.                 addedUnfinishedEntry = true;
  86.             }
  87.             return expandCodeToOutputStack(effectiveCode, addedUnfinishedEntry);
  88.         }
  89.         final int subCode = readNextCode();
  90.         if (subCode < 0) {
  91.             throw new IOException("Unexpected EOF;");
  92.         }
  93.         if (subCode == 1) {
  94.             if (getCodeSize() >= MAX_CODE_SIZE) {
  95.                 throw new IOException("Attempt to increase code size beyond maximum");
  96.             }
  97.             incrementCodeSize();
  98.         } else if (subCode == 2) {
  99.             partialClear();
  100.             setTableSize(getClearCode() + 1);
  101.         } else {
  102.             throw new IOException("Invalid clear code subcode " + subCode);
  103.         }
  104.         return 0;
  105.     }

  106.     private void partialClear() {
  107.         final boolean[] isParent = new boolean[MAX_TABLE_SIZE];
  108.         for (int i = 0; i < isUsed.length; i++) {
  109.             if (isUsed[i] && getPrefix(i) != UNUSED_PREFIX) {
  110.                 isParent[getPrefix(i)] = true;
  111.             }
  112.         }
  113.         for (int i = getClearCode() + 1; i < isParent.length; i++) {
  114.             if (!isParent[i]) {
  115.                 isUsed[i] = false;
  116.                 setPrefix(i, UNUSED_PREFIX);
  117.             }
  118.         }
  119.     }
  120. }