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}