/* * 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 java.util.zip; import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.util.Arrays; /** * This class decompresses data that was compressed using the <i>DEFLATE</i> * algorithm (see <a href="http://www.gzip.org/algorithm.txt">specification</a>). * * <p>It is usually more convenient to use {@link InflaterInputStream}. * * <p>To decompress an in-memory {@code byte[]} to another in-memory {@code byte[]} manually: * <pre> * byte[] compressedBytes = ... * int decompressedByteCount = ... // From your format's metadata. * Inflater inflater = new Inflater(); * inflater.setInput(compressedBytes, 0, compressedBytes.length); * byte[] decompressedBytes = new byte[decompressedByteCount]; * if (inflater.inflate(decompressedBytes) != decompressedByteCount) { * throw new AssertionError(); * } * inflater.end(); * </pre> * <p>In situations where you don't have all the input in one array (or have so much * input that you want to feed it to the inflater in chunks), it's possible to call * {@link #setInput} repeatedly, but you're much better off using {@link InflaterInputStream} * to handle all this for you. * * <p>If you don't know how big the decompressed data will be, you can call {@link #inflate} * repeatedly on a temporary buffer, copying the bytes to a {@link java.io.ByteArrayOutputStream}, * but this is probably another sign you'd be better off using {@link InflaterInputStream}. */ public class Inflater { private int inLength; private int inRead; // Set by inflateImpl. private boolean finished; // Set by inflateImpl. private boolean needsDictionary; // Set by inflateImpl. private long streamHandle = -1; private final CloseGuard guard = CloseGuard.get(); /** * This constructor creates an inflater that expects a header from the input * stream. Use {@link #Inflater(boolean)} if the input comes without a ZLIB * header. */ public Inflater() { this(false); } /** * This constructor allows to create an inflater that expects no header from * the input stream. * * @param noHeader * {@code true} indicates that no ZLIB header comes with the * input. */ public Inflater(boolean noHeader) { streamHandle = createStream(noHeader); guard.open("end"); } private native long createStream(boolean noHeader1); /** * Releases resources associated with this {@code Inflater}. Any unused * input or output is discarded. This method should be called explicitly in * order to free native resources as soon as possible. After {@code end()} is * called, other methods will typically throw {@code IllegalStateException}. */ public synchronized void end() { guard.close(); if (streamHandle != -1) { endImpl(streamHandle); inRead = 0; inLength = 0; streamHandle = -1; } } private native void endImpl(long handle); @Override protected void finalize() { try { if (guard != null) { guard.warnIfOpen(); } end(); } finally { try { super.finalize(); } catch (Throwable t) { throw new AssertionError(t); } } } /** * Indicates if the {@code Inflater} has inflated the entire deflated * stream. If deflated bytes remain and {@link #needsInput} returns {@code * true} this method will return {@code false}. This method should be * called after all deflated input is supplied to the {@code Inflater}. * * @return {@code true} if all input has been inflated, {@code false} * otherwise. */ public synchronized boolean finished() { return finished; } /** * Returns the {@link Adler32} checksum of the bytes inflated so far, or the * checksum of the preset dictionary if {@link #needsDictionary} returns true. */ public synchronized int getAdler() { checkOpen(); return getAdlerImpl(streamHandle); } private native int getAdlerImpl(long handle); /** * Returns the total number of bytes read by the {@code Inflater}. This * method is the same as {@link #getTotalIn} except that it returns a * {@code long} value instead of an integer. */ public synchronized long getBytesRead() { checkOpen(); return getTotalInImpl(streamHandle); } /** * Returns a the total number of bytes written by this {@code Inflater}. This * method is the same as {@code getTotalOut} except it returns a * {@code long} value instead of an integer. */ public synchronized long getBytesWritten() { checkOpen(); return getTotalOutImpl(streamHandle); } /** * Returns the number of bytes of current input remaining to be read by this * inflater. */ public synchronized int getRemaining() { return inLength - inRead; } /** * Returns the total number of bytes of input read by this {@code Inflater}. This * method is limited to 32 bits; use {@link #getBytesRead} instead. */ public synchronized int getTotalIn() { checkOpen(); return (int) Math.min(getTotalInImpl(streamHandle), (long) Integer.MAX_VALUE); } private native long getTotalInImpl(long handle); /** * Returns the total number of bytes written to the output buffer by this {@code * Inflater}. The method is limited to 32 bits; use {@link #getBytesWritten} instead. */ public synchronized int getTotalOut() { checkOpen(); return (int) Math.min(getTotalOutImpl(streamHandle), (long) Integer.MAX_VALUE); } private native long getTotalOutImpl(long handle); /** * Inflates bytes from the current input and stores them in {@code buf}. * * @param buf * the buffer where decompressed data bytes are written. * @return the number of bytes inflated. * @throws DataFormatException * if the underlying stream is corrupted or was not compressed * using a {@code Deflater}. */ public int inflate(byte[] buf) throws DataFormatException { return inflate(buf, 0, buf.length); } /** * Inflates up to {@code byteCount} bytes from the current input and stores them in * {@code buf} starting at {@code offset}. * * @throws DataFormatException * if the underlying stream is corrupted or was not compressed * using a {@code Deflater}. * @return the number of bytes inflated. */ public synchronized int inflate(byte[] buf, int offset, int byteCount) throws DataFormatException { Arrays.checkOffsetAndCount(buf.length, offset, byteCount); checkOpen(); if (needsInput()) { return 0; } boolean neededDict = needsDictionary; needsDictionary = false; int result = inflateImpl(buf, offset, byteCount, streamHandle); if (needsDictionary && neededDict) { throw new DataFormatException("Needs dictionary"); } return result; } private native int inflateImpl(byte[] buf, int offset, int byteCount, long handle); /** * Returns true if the input bytes were compressed with a preset * dictionary. This method should be called if the first call to {@link #inflate} returns 0, * to determine whether a dictionary is required. If so, {@link #setDictionary} * should be called with the appropriate dictionary before calling {@code * inflate} again. Use {@link #getAdler} to determine which dictionary is required. */ public synchronized boolean needsDictionary() { return needsDictionary; } /** * Returns true if {@link #setInput} must be called before inflation can continue. */ public synchronized boolean needsInput() { return inRead == inLength; } /** * Resets this {@code Inflater}. Should be called prior to inflating a new * set of data. */ public synchronized void reset() { checkOpen(); finished = false; needsDictionary = false; inLength = inRead = 0; resetImpl(streamHandle); } private native void resetImpl(long handle); /** * Sets the preset dictionary to be used for inflation to {@code dictionary}. * See {@link #needsDictionary} for details. */ public synchronized void setDictionary(byte[] dictionary) { setDictionary(dictionary, 0, dictionary.length); } /** * Sets the preset dictionary to be used for inflation to a subsequence of {@code dictionary} * starting at {@code offset} and continuing for {@code byteCount} bytes. See {@link * #needsDictionary} for details. */ public synchronized void setDictionary(byte[] dictionary, int offset, int byteCount) { checkOpen(); Arrays.checkOffsetAndCount(dictionary.length, offset, byteCount); setDictionaryImpl(dictionary, offset, byteCount, streamHandle); } private native void setDictionaryImpl(byte[] dictionary, int offset, int byteCount, long handle); /** * Sets the current input to to be decompressed. This method should only be * called if {@link #needsInput} returns {@code true}. */ public synchronized void setInput(byte[] buf) { setInput(buf, 0, buf.length); } /** * Sets the current input to to be decompressed. This method should only be * called if {@link #needsInput} returns {@code true}. */ public synchronized void setInput(byte[] buf, int offset, int byteCount) { checkOpen(); Arrays.checkOffsetAndCount(buf.length, offset, byteCount); inRead = 0; inLength = byteCount; setInputImpl(buf, offset, byteCount, streamHandle); } private native void setInputImpl(byte[] buf, int offset, int byteCount, long handle); synchronized int setFileInput(FileDescriptor fd, long offset, int byteCount) { checkOpen(); inRead = 0; inLength = setFileInputImpl(fd, offset, byteCount, streamHandle); return inLength; } private native int setFileInputImpl(FileDescriptor fd, long offset, int byteCount, long handle); private void checkOpen() { if (streamHandle == -1) { throw new IllegalStateException("attempt to use Inflater after calling end"); } } }