001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.compressors.z; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.nio.ByteOrder; 024 025import org.apache.commons.compress.compressors.lzw.LZWInputStream; 026 027/** 028 * Input stream that decompresses .Z files. 029 * @NotThreadSafe 030 * @since 1.7 031 */ 032public class ZCompressorInputStream extends LZWInputStream { 033 private static final int MAGIC_1 = 0x1f; 034 private static final int MAGIC_2 = 0x9d; 035 private static final int BLOCK_MODE_MASK = 0x80; 036 private static final int MAX_CODE_SIZE_MASK = 0x1f; 037 private final boolean blockMode; 038 private final int maxCodeSize; 039 private long totalCodesRead = 0; 040 041 public ZCompressorInputStream(final InputStream inputStream) throws IOException { 042 super(inputStream, ByteOrder.LITTLE_ENDIAN); 043 final int firstByte = (int) in.readBits(8); 044 final int secondByte = (int) in.readBits(8); 045 final int thirdByte = (int) in.readBits(8); 046 if (firstByte != MAGIC_1 || secondByte != MAGIC_2 || thirdByte < 0) { 047 throw new IOException("Input is not in .Z format"); 048 } 049 blockMode = (thirdByte & BLOCK_MODE_MASK) != 0; 050 maxCodeSize = thirdByte & MAX_CODE_SIZE_MASK; 051 if (blockMode) { 052 setClearCode(DEFAULT_CODE_SIZE); 053 } 054 initializeTables(maxCodeSize); 055 clearEntries(); 056 } 057 058 private void clearEntries() { 059 setTableSize((1 << 8) + (blockMode ? 1 : 0)); 060 } 061 062 /** 063 * {@inheritDoc} 064 * <p><strong>This method is only protected for technical reasons 065 * and is not part of Commons Compress' published API. It may 066 * change or disappear without warning.</strong></p> 067 */ 068 @Override 069 protected int readNextCode() throws IOException { 070 final int code = super.readNextCode(); 071 if (code >= 0) { 072 ++totalCodesRead; 073 } 074 return code; 075 } 076 077 private void reAlignReading() throws IOException { 078 // "compress" works in multiples of 8 symbols, each codeBits bits long. 079 // When codeBits changes, the remaining unused symbols in the current 080 // group of 8 are still written out, in the old codeSize, 081 // as garbage values (usually zeroes) that need to be skipped. 082 long codeReadsToThrowAway = 8 - (totalCodesRead % 8); 083 if (codeReadsToThrowAway == 8) { 084 codeReadsToThrowAway = 0; 085 } 086 for (long i = 0; i < codeReadsToThrowAway; i++) { 087 readNextCode(); 088 } 089 in.clearBitCache(); 090 } 091 092 /** 093 * {@inheritDoc} 094 * <p><strong>This method is only protected for technical reasons 095 * and is not part of Commons Compress' published API. It may 096 * change or disappear without warning.</strong></p> 097 */ 098 @Override 099 protected int addEntry(final int previousCode, final byte character) throws IOException { 100 final int maxTableSize = 1 << getCodeSize(); 101 final int r = addEntry(previousCode, character, maxTableSize); 102 if (getTableSize() == maxTableSize && getCodeSize() < maxCodeSize) { 103 reAlignReading(); 104 incrementCodeSize(); 105 } 106 return r; 107 } 108 109 /** 110 * {@inheritDoc} 111 * <p><strong>This method is only protected for technical reasons 112 * and is not part of Commons Compress' published API. It may 113 * change or disappear without warning.</strong></p> 114 */ 115 @Override 116 protected int decompressNextSymbol() throws IOException { 117 // 118 // table entry table entry 119 // _____________ _____ 120 // table entry / \ / \ 121 // ____________/ \ \ 122 // / / \ / \ \ 123 // +---+---+---+---+---+---+---+---+---+---+ 124 // | . | . | . | . | . | . | . | . | . | . | 125 // +---+---+---+---+---+---+---+---+---+---+ 126 // |<--------->|<------------->|<----->|<->| 127 // symbol symbol symbol symbol 128 // 129 final int code = readNextCode(); 130 if (code < 0) { 131 return -1; 132 } else if (blockMode && code == getClearCode()) { 133 clearEntries(); 134 reAlignReading(); 135 resetCodeSize(); 136 resetPreviousCode(); 137 return 0; 138 } else { 139 boolean addedUnfinishedEntry = false; 140 if (code == getTableSize()) { 141 addRepeatOfPreviousCode(); 142 addedUnfinishedEntry = true; 143 } else if (code > getTableSize()) { 144 throw new IOException(String.format("Invalid %d bit code 0x%x", getCodeSize(), code)); 145 } 146 return expandCodeToOutputStack(code, addedUnfinishedEntry); 147 } 148 } 149 150 /** 151 * Checks if the signature matches what is expected for a Unix compress file. 152 * 153 * @param signature 154 * the bytes to check 155 * @param length 156 * the number of bytes to check 157 * @return true, if this stream is a Unix compress compressed 158 * stream, false otherwise 159 * 160 * @since 1.9 161 */ 162 public static boolean matches(final byte[] signature, final int length) { 163 return length > 3 && signature[0] == MAGIC_1 && signature[1] == (byte) MAGIC_2; 164 } 165 166}