/* * 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.zip; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; import org.apache.commons.compress.compressors.lzw.LZWInputStream; /** * Input stream that decompresses ZIP method 1 (unshrinking). A variation of the LZW algorithm, with some twists. * @NotThreadSafe * @since 1.7 */ class UnshrinkingInputStream extends LZWInputStream { private static final int MAX_CODE_SIZE = 13; private static final int MAX_TABLE_SIZE = 1 << MAX_CODE_SIZE; private final boolean[] isUsed; /** * IOException is not actually thrown! * * @param inputStream * @throws IOException IOException is not actually thrown! */ public UnshrinkingInputStream(final InputStream inputStream) throws IOException { super(inputStream, ByteOrder.LITTLE_ENDIAN); setClearCode(DEFAULT_CODE_SIZE); initializeTables(MAX_CODE_SIZE); isUsed = new boolean[getPrefixesLength()]; for (int i = 0; i < (1 << 8); i++) { isUsed[i] = true; } setTableSize(getClearCode() + 1); } @Override protected int addEntry(final int previousCode, final byte character) throws IOException { int tableSize = getTableSize(); while ((tableSize < MAX_TABLE_SIZE) && isUsed[tableSize]) { tableSize++; } setTableSize(tableSize); final int idx = addEntry(previousCode, character, MAX_TABLE_SIZE); if (idx >= 0) { isUsed[idx] = true; } return idx; } private void partialClear() { final boolean[] isParent = new boolean[MAX_TABLE_SIZE]; for (int i = 0; i < isUsed.length; i++) { if (isUsed[i] && getPrefix(i) != UNUSED_PREFIX) { isParent[getPrefix(i)] = true; } } for (int i = getClearCode() + 1; i < isParent.length; i++) { if (!isParent[i]) { isUsed[i] = false; setPrefix(i, UNUSED_PREFIX); } } } @Override protected int decompressNextSymbol() throws IOException { // // table entry table entry // _____________ _____ // table entry / \ / \ // ____________/ \ \ // / / \ / \ \ // +---+---+---+---+---+---+---+---+---+---+ // | . | . | . | . | . | . | . | . | . | . | // +---+---+---+---+---+---+---+---+---+---+ // |<--------->|<------------->|<----->|<->| // symbol symbol symbol symbol // final int code = readNextCode(); if (code < 0) { return -1; } else if (code == getClearCode()) { final int subCode = readNextCode(); if (subCode < 0) { throw new IOException("Unexpected EOF;"); } else if (subCode == 1) { if (getCodeSize() < MAX_CODE_SIZE) { incrementCodeSize(); } else { throw new IOException("Attempt to increase code size beyond maximum"); } } else if (subCode == 2) { partialClear(); setTableSize(getClearCode() + 1); } else { throw new IOException("Invalid clear code subcode " + subCode); } return 0; } else { boolean addedUnfinishedEntry = false; int effectiveCode = code; if (!isUsed[code]) { effectiveCode = addRepeatOfPreviousCode(); addedUnfinishedEntry = true; } return expandCodeToOutputStack(effectiveCode, addedUnfinishedEntry); } } }