ZCompressorInputStream.java
/*
* 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.z;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import org.apache.commons.compress.compressors.lzw.LZWInputStream;
/**
* Input stream that decompresses .Z files.
*
* @NotThreadSafe
* @since 1.7
*/
public class ZCompressorInputStream extends LZWInputStream {
private static final int MAGIC_1 = 0x1f;
private static final int MAGIC_2 = 0x9d;
private static final int BLOCK_MODE_MASK = 0x80;
private static final int MAX_CODE_SIZE_MASK = 0x1f;
/**
* Checks if the signature matches what is expected for a UNIX compress file.
*
* @param signature the bytes to check
* @param length the number of bytes to check
* @return true, if this stream is a UNIX compress compressed stream, false otherwise
*
* @since 1.9
*/
public static boolean matches(final byte[] signature, final int length) {
return length > 3 && signature[0] == MAGIC_1 && signature[1] == (byte) MAGIC_2;
}
private final boolean blockMode;
private final int maxCodeSize;
private long totalCodesRead;
public ZCompressorInputStream(final InputStream inputStream) throws IOException {
this(inputStream, -1);
}
public ZCompressorInputStream(final InputStream inputStream, final int memoryLimitInKb) throws IOException {
super(inputStream, ByteOrder.LITTLE_ENDIAN);
final int firstByte = (int) in.readBits(8);
final int secondByte = (int) in.readBits(8);
final int thirdByte = (int) in.readBits(8);
if (firstByte != MAGIC_1 || secondByte != MAGIC_2 || thirdByte < 0) {
throw new IOException("Input is not in .Z format");
}
blockMode = (thirdByte & BLOCK_MODE_MASK) != 0;
maxCodeSize = thirdByte & MAX_CODE_SIZE_MASK;
if (blockMode) {
setClearCode(DEFAULT_CODE_SIZE);
}
initializeTables(maxCodeSize, memoryLimitInKb);
clearEntries();
}
/**
* {@inheritDoc}
* <p>
* <strong>This method is only protected for technical reasons and is not part of Commons Compress' published API. It may change or disappear without
* warning.</strong>
* </p>
*/
@Override
protected int addEntry(final int previousCode, final byte character) throws IOException {
final int maxTableSize = 1 << getCodeSize();
final int r = addEntry(previousCode, character, maxTableSize);
if (getTableSize() == maxTableSize && getCodeSize() < maxCodeSize) {
reAlignReading();
incrementCodeSize();
}
return r;
}
private void clearEntries() {
setTableSize((1 << 8) + (blockMode ? 1 : 0));
}
/**
* {@inheritDoc}
* <p>
* <strong>This method is only protected for technical reasons and is not part of Commons Compress' published API. It may change or disappear without
* warning.</strong>
* </p>
*/
@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;
}
if (blockMode && code == getClearCode()) {
clearEntries();
reAlignReading();
resetCodeSize();
resetPreviousCode();
return 0;
}
boolean addedUnfinishedEntry = false;
if (code == getTableSize()) {
addRepeatOfPreviousCode();
addedUnfinishedEntry = true;
} else if (code > getTableSize()) {
throw new IOException(String.format("Invalid %d bit code 0x%x", getCodeSize(), code));
}
return expandCodeToOutputStack(code, addedUnfinishedEntry);
}
/**
* {@inheritDoc}
* <p>
* <strong>This method is only protected for technical reasons and is not part of Commons Compress' published API. It may change or disappear without
* warning.</strong>
* </p>
*/
@Override
protected int readNextCode() throws IOException {
final int code = super.readNextCode();
if (code >= 0) {
++totalCodesRead;
}
return code;
}
private void reAlignReading() throws IOException {
// "compress" works in multiples of 8 symbols, each codeBits bits long.
// When codeBits changes, the remaining unused symbols in the current
// group of 8 are still written out, in the old codeSize,
// as garbage values (usually zeroes) that need to be skipped.
long codeReadsToThrowAway = 8 - totalCodesRead % 8;
if (codeReadsToThrowAway == 8) {
codeReadsToThrowAway = 0;
}
for (long i = 0; i < codeReadsToThrowAway; i++) {
readNextCode();
}
in.clearBitCache();
}
}