/* * 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 "zlib.h" #import "java/io/FileInputStream.h" #import "java/io/IOException.h" #import "java/lang/IllegalArgumentException.h" #import "java/lang/OutOfMemoryError.h" #define DEF_WBITS 15 ]-*/ 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}. * * Ported to j2objc by Alexander Jarvis */ 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 long inBuffer = 0L; // Address to malloc'd memory for next_in. 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 noHeader) /*-[ z_stream *zStream = (z_stream *) malloc(sizeof(z_stream)); zStream->opaque = Z_NULL; zStream->zalloc = Z_NULL; zStream->zfree = Z_NULL; zStream->adler = 1; int err = inflateInit2(zStream, noHeader ? -DEF_WBITS : DEF_WBITS); if (err != Z_OK) { free(zStream); @throw AUTORELEASE([[JavaLangIllegalArgumentException alloc] init]); } return (long long) zStream; ]-*/; /** * 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) /*-[ z_stream *zStream = (z_stream*) handle; inflateEnd(zStream); free((void*) self->inBuffer_); free(zStream); ]-*/; @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) /*-[ z_stream *zStream = (z_stream*) handle; return (int) zStream->adler; ]-*/; /** * 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 offset of the next byte to read in the underlying buffer. * * For internal use only. */ synchronized int getCurrentOffset() { return 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) /*-[ z_stream *zStream = (z_stream*) handle; return zStream->total_in; ]-*/; /** * 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) /*-[ z_stream *zStream = (z_stream*) handle; return zStream->total_out; ]-*/; /** * 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) /*-[ z_stream *zStream = (z_stream*) handle; if (!buf) { return -1; } if (buf->size_ > 0) { zStream->next_out = (Bytef *) [buf byteRefAtIndex:offset]; } zStream->avail_out = byteCount; Bytef *initialNextIn = zStream->next_in; Bytef *initialNextOut = zStream->next_out; int err = inflate(zStream, Z_SYNC_FLUSH); switch (err) { case Z_OK: break; case Z_NEED_DICT: self->needsDictionary_ = YES; break; case Z_STREAM_END: self->finished_ = YES; break; case Z_STREAM_ERROR: return 0; default: @throw AUTORELEASE([[JavaUtilZipDataFormatException alloc] init]); } int bytesRead = (int) (zStream->next_in - initialNextIn); int bytesWritten = (int) (zStream->next_out - initialNextOut); self->inRead_ += bytesRead; return bytesWritten; ]-*/; /** * 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) /*-[ z_stream *zStream = (z_stream *) handle; int err = 0; err = inflateReset(zStream); if (err != Z_OK) { @throw AUTORELEASE([[JavaLangIllegalArgumentException alloc] init]); } ]-*/; /** * 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[] buf, int offset, int byteCount, long handle) /*-[ z_stream *zStream = (z_stream*) handle; const Bytef *dictionary = (const Bytef *) [buf byteRefAtIndex:offset]; int err = inflateSetDictionary(zStream, dictionary, byteCount); if (err != Z_OK) { @throw AUTORELEASE([[JavaLangIllegalArgumentException alloc] init]); } ]-*/; /** * 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) /*-[ z_stream *zStream = (z_stream *) handle; char *baseAddr = (char *) malloc(byteCount); if (baseAddr == NULL) { @throw AUTORELEASE([[JavaLangOutOfMemoryError alloc] init]); } if (self->inBuffer_ != 0L) { free((void *) self->inBuffer_); } self->inBuffer_ = (long long) baseAddr; zStream->next_in = (Bytef *) baseAddr; zStream->avail_in = byteCount; if (byteCount > 0) { memcpy(baseAddr, [buf byteRefAtIndex:offset], byteCount); } ]-*/; 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) /*-[ JavaIoFileInputStream *fileIn = [[JavaIoFileInputStream alloc] initWithJavaIoFileDescriptor:fd]; lseek([fd getInt$], offset, SEEK_SET); IOSByteArray *in = [IOSByteArray arrayWithLength:byteCount]; jint bytesRead = [fileIn readWithByteArray:in withInt:0 withInt:byteCount]; if (bytesRead < 0) { @throw AUTORELEASE([[JavaIoIOException alloc] init]); } [self setInputImplWithByteArray:in withInt:0 withInt:byteCount withLong:handle]; [fileIn close]; RELEASE_(fileIn); return byteCount; ]-*/; private void checkOpen() { if (streamHandle == -1) { throw new IllegalStateException("attempt to use Inflater after calling end"); } } }