summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--luni/src/main/java/java/util/Arrays.java13
-rw-r--r--luni/src/main/java/java/util/zip/Deflater.java310
-rw-r--r--luni/src/main/java/java/util/zip/DeflaterOutputStream.java10
-rw-r--r--luni/src/main/java/java/util/zip/Inflater.java264
-rw-r--r--luni/src/main/java/java/util/zip/InflaterInputStream.java3
-rw-r--r--luni/src/main/java/java/util/zip/ZipInputStream.java12
-rw-r--r--luni/src/main/java/java/util/zip/ZipOutputStream.java56
-rw-r--r--luni/src/main/native/java_util_zip_Deflater.cpp84
-rw-r--r--luni/src/main/native/java_util_zip_Inflater.cpp85
-rw-r--r--luni/src/main/native/zip.h1
-rw-r--r--luni/src/test/java/libcore/java/util/zip/DeflaterOutputStreamTest.java3
-rw-r--r--luni/src/test/java/libcore/java/util/zip/InflaterTest.java111
12 files changed, 433 insertions, 519 deletions
diff --git a/luni/src/main/java/java/util/Arrays.java b/luni/src/main/java/java/util/Arrays.java
index 5f38db3..80120d2 100644
--- a/luni/src/main/java/java/util/Arrays.java
+++ b/luni/src/main/java/java/util/Arrays.java
@@ -1729,6 +1729,19 @@ public class Arrays {
DualPivotQuicksort.sort(array, start, end);
}
+ /**
+ * Checks that the range described by {@code offset} and {@code count} doesn't exceed
+ * {@code arrayLength}.
+ *
+ * @hide
+ */
+ public static void checkOffsetAndCount(int arrayLength, int offset, int count) {
+ if (offset > arrayLength || count < 0 || offset < 0 || arrayLength - offset < count) {
+ throw new ArrayIndexOutOfBoundsException("offset=" + offset + ", count=" + count +
+ ", array length=" + arrayLength);
+ }
+ }
+
private static void checkFillBounds(int arrLength, int start, int end) {
if (start > end) {
throw new IllegalArgumentException("start < end: " + start + " < " + end);
diff --git a/luni/src/main/java/java/util/zip/Deflater.java b/luni/src/main/java/java/util/zip/Deflater.java
index 4442333..e93d615 100644
--- a/luni/src/main/java/java/util/zip/Deflater.java
+++ b/luni/src/main/java/java/util/zip/Deflater.java
@@ -18,22 +18,38 @@
package java.util.zip;
import dalvik.system.CloseGuard;
+import java.util.Arrays;
import libcore.base.EmptyArray;
/**
* This class compresses data using the <i>DEFLATE</i> algorithm (see <a
* href="http://www.gzip.org/algorithm.txt">specification</a>).
- * <p>
- * Basically this class is part of the API to the stream based ZLIB compression
- * library and is used as such by {@code DeflaterOutputStream} and its
- * descendants.
- * <p>
- * The typical usage of a {@code Deflater} instance outside this package
- * consists of a specific call to one of its constructors before being passed to
- * an instance of {@code DeflaterOutputStream}.
*
- * @see DeflaterOutputStream
- * @see Inflater
+ * <p>It is usually more convenient to use {@link DeflaterOutputStream}.
+ *
+ * <p>To compress an in-memory {@code byte[]} to another in-memory {@code byte[]} manually:
+ * <pre>
+ * byte[] originalBytes = ...
+ *
+ * Deflater deflater = new Deflater();
+ * deflater.setInput(originalBytes);
+ * deflater.finish();
+ *
+ * ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ * byte[] buf = new byte[8192];
+ * while (!deflater.finished()) {
+ * int byteCount = deflater.deflate(buf);
+ * baos.write(buf, 0, byteCount);
+ * }
+ * deflater.end();
+ *
+ * byte[] compressedBytes = baos.toByteArray();
+ * </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 deflater in chunks), it's possible to call
+ * {@link #setInput} repeatedly, but you're much better off using {@link DeflaterOutputStream}
+ * to handle all this for you. {@link DeflaterOutputStream} also helps minimize memory
+ * requirements&nbsp;&mdash; the sample code above is very expensive.
*/
public class Deflater {
@@ -48,32 +64,32 @@ public class Deflater {
public static final int BEST_SPEED = 1;
/**
- * Usage of the default compression level.
+ * The default compression level.
*/
public static final int DEFAULT_COMPRESSION = -1;
/**
- * Default value for compression strategy.
+ * The default compression strategy.
*/
public static final int DEFAULT_STRATEGY = 0;
/**
- * Default value for compression method.
+ * The default compression method.
*/
public static final int DEFLATED = 8;
/**
- * Possible value for compression strategy.
+ * A compression strategy.
*/
public static final int FILTERED = 1;
/**
- * Possible value for compression strategy.
+ * A compression strategy.
*/
public static final int HUFFMAN_ONLY = 2;
/**
- * Possible value for compression level.
+ * A compression level.
*/
public static final int NO_COMPRESSION = 0;
@@ -106,11 +122,11 @@ public class Deflater {
public static final int FULL_FLUSH = 3;
/**
- * Flush buffers and mark the end of the datastream.
+ * Flush buffers and mark the end of the data stream.
*/
private static final int FINISH = 4;
- private int flushParm = NO_FLUSH;
+ private int flushStyle = NO_FLUSH;
private boolean finished;
@@ -129,20 +145,20 @@ public class Deflater {
private final CloseGuard guard = CloseGuard.get();
/**
- * Constructs a new {@code Deflater} instance with default compression
- * level. The strategy can be specified with {@link #setStrategy}, only. A
- * header is added to the output by default; use constructor {@code
- * Deflater(level, boolean)} if you need to omit the header.
+ * Constructs a new {@code Deflater} instance using the default compression
+ * level. The strategy can be specified with {@link #setStrategy}. A
+ * header is added to the output by default; use {@link
+ * #Deflater(int, boolean)} if you need to omit the header.
*/
public Deflater() {
this(DEFAULT_COMPRESSION, false);
}
/**
- * Constructs a new {@code Deflater} instance with a specific compression
- * level. The strategy can be specified with {@code setStrategy}, only. A
- * header is added to the output by default; use
- * {@code Deflater(level, boolean)} if you need to omit the header.
+ * Constructs a new {@code Deflater} instance using compression
+ * level {@code level}. The strategy can be specified with {@link #setStrategy}.
+ * A header is added to the output by default; use
+ * {@link #Deflater(int, boolean)} if you need to omit the header.
*
* @param level
* the compression level in the range between 0 and 9.
@@ -153,9 +169,9 @@ public class Deflater {
/**
* Constructs a new {@code Deflater} instance with a specific compression
- * level. If noHeader is passed as true no ZLib header is added to the
+ * level. If {@code noHeader} is true, no ZLIB header is added to the
* output. In a ZIP archive every entry (compressed file) comes with such a
- * header. The strategy can be specified with the setStrategy method, only.
+ * header. The strategy can be specified using {@link #setStrategy}.
*
* @param level
* the compression level in the range between 0 and 9.
@@ -173,83 +189,63 @@ public class Deflater {
}
/**
- * Deflates the data (previously passed to {@code setInput}) into the
+ * Deflates the data (previously passed to {@link #setInput}) into the
* supplied buffer.
*
- * @param buf
- * buffer to write compressed data to.
* @return number of bytes of compressed data written to {@code buf}.
- * @see #deflate(byte[], int, int)
*/
public int deflate(byte[] buf) {
return deflate(buf, 0, buf.length);
}
/**
- * Deflates data (previously passed to {@code setInput}) into a specific
+ * Deflates data (previously passed to {@link #setInput}) into a specific
* region within the supplied buffer.
*
- * @param buf
- * the buffer to write compressed data to.
- * @param off
- * the offset within {@code buf} at which to start writing to.
- * @param nbytes
- * maximum number of bytes of compressed data to be written.
* @return the number of bytes of compressed data written to {@code buf}.
*/
- public synchronized int deflate(byte[] buf, int off, int nbytes) {
- return deflateImpl(buf, off, nbytes, flushParm);
+ public synchronized int deflate(byte[] buf, int offset, int byteCount) {
+ return deflateImpl(buf, offset, byteCount, flushStyle);
}
/**
- * Deflates data (previously passed to {@code setInput}) into a specific
+ * Deflates data (previously passed to {@link #setInput}) into a specific
* region within the supplied buffer, optionally flushing the input buffer.
*
- * @param buf the buffer to write compressed data to.
- * @param off the offset within {@code buf} at which to start writing to.
- * @param nbytes maximum number of bytes of compressed data to be written.
- * @param flush one of {@link #NO_FLUSH}, {@link #SYNC_FLUSH} or
- * {@link #FULL_FLUSH}.
+ * @param flush one of {@link #NO_FLUSH}, {@link #SYNC_FLUSH} or {@link #FULL_FLUSH}.
* @return the number of compressed bytes written to {@code buf}. If this
- * equals {@code nbytes}, the number of bytes of input to be flushed
+ * equals {@code byteCount}, the number of bytes of input to be flushed
* may have exceeded the output buffer's capacity. In this case,
* finishing a flush will require the output buffer to be drained
* and additional calls to {@link #deflate} to be made.
* @hide
* @since 1.7
*/
- public synchronized int deflate(byte[] buf, int off, int nbytes, int flush) {
+ public synchronized int deflate(byte[] buf, int offset, int byteCount, int flush) {
if (flush != NO_FLUSH && flush != SYNC_FLUSH && flush != FULL_FLUSH) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Bad flush value: " + flush);
}
- return deflateImpl(buf, off, nbytes, flush);
+ return deflateImpl(buf, offset, byteCount, flush);
}
- private synchronized int deflateImpl(
- byte[] buf, int off, int nbytes, int flush) {
+ private synchronized int deflateImpl(byte[] buf, int offset, int byteCount, int flush) {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- if (off > buf.length || nbytes < 0 || off < 0 || buf.length - off < nbytes) {
- throw new ArrayIndexOutOfBoundsException();
- }
+ Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
if (inputBuffer == null) {
setInput(EmptyArray.BYTE);
}
- return deflateImpl(buf, off, nbytes, streamHandle, flush);
+ return deflateImpl(buf, offset, byteCount, streamHandle, flush);
}
- private synchronized native int deflateImpl(byte[] buf, int off,
- int nbytes, long handle, int flushParm1);
-
- private synchronized native void endImpl(long handle);
+ private native int deflateImpl(byte[] buf, int offset, int byteCount, long handle, int flushStyle);
/**
* Frees all resources held onto by this deflating algorithm. Any unused
- * input or output is discarded. While this method is used by {@code
- * finalize()}, it can be called explicitly in order to free native
- * resources before the next GC cycle. After {@code end()} was called other
- * methods will typically throw an {@code IllegalStateException}.
+ * 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();
@@ -264,6 +260,8 @@ public class Deflater {
}
}
+ private native void endImpl(long handle);
+
@Override protected void finalize() {
try {
if (guard != null) {
@@ -289,28 +287,18 @@ public class Deflater {
* @see #finished
*/
public synchronized void finish() {
- flushParm = FINISH;
+ flushStyle = FINISH;
}
/**
- * Returns whether or not all provided data has been successfully
- * compressed.
- *
- * @return true if all data has been compressed, false otherwise.
+ * Returns true if all provided data has been successfully compressed.
*/
public synchronized boolean finished() {
return finished;
}
/**
- * Returns the Adler32 checksum of uncompressed data currently read. If a
- * preset dictionary is used getAdler() will return the Adler32 checksum of
- * the dictionary used.
- *
- * @return the Adler32 checksum of uncompressed data or preset dictionary if
- * used.
- * @see #setDictionary(byte[])
- * @see #setDictionary(byte[], int, int)
+ * Returns the {@link #Adler32} checksum of the uncompressed data read so far.
*/
public synchronized int getAdler() {
if (streamHandle == -1) {
@@ -320,50 +308,38 @@ public class Deflater {
return getAdlerImpl(streamHandle);
}
- private synchronized native int getAdlerImpl(long handle);
+ private native int getAdlerImpl(long handle);
/**
- * Returns the total number of bytes of input consumed by the {@code Deflater}.
- *
- * @return number of bytes of input read.
+ * Returns the total number of bytes of input read by this {@code Deflater}. This
+ * method is limited to 32 bits; use {@link #getBytesRead} instead.
*/
public synchronized int getTotalIn() {
if (streamHandle == -1) {
throw new IllegalStateException();
}
-
return (int) getTotalInImpl(streamHandle);
}
- private synchronized native long getTotalInImpl(long handle);
+ private native long getTotalInImpl(long handle);
/**
- * Returns the total number of compressed bytes output by this {@code Deflater}.
- *
- * @return number of compressed bytes output.
+ * Returns the total number of bytes written to the output buffer by this {@code
+ * Deflater}. The method is limited to 32 bits; use {@link #getBytesWritten} instead.
*/
public synchronized int getTotalOut() {
if (streamHandle == -1) {
throw new IllegalStateException();
}
-
return (int) getTotalOutImpl(streamHandle);
}
- private synchronized native long getTotalOutImpl(long handle);
+ private native long getTotalOutImpl(long handle);
/**
- * Counterpart to setInput(). Indicates whether or not all bytes of
- * uncompressed input have been consumed by the {@code Deflater}. If needsInput()
- * returns true setInput() must be called before deflation can continue. If
- * all bytes of uncompressed data have been provided to the {@code Deflater}
- * finish() must be called to ensure the compressed data is output.
- *
- * @return {@code true} if input is required for deflation to continue,
- * {@code false} otherwise.
- * @see #finished()
- * @see #setInput(byte[])
- * @see #setInput(byte[], int, int)
+ * Returns true if {@link #setInput} must be called before deflation can continue.
+ * If all uncompressed data has been provided to the {@code Deflater},
+ * {@link #finish} must be called to ensure the compressed data is output.
*/
public synchronized boolean needsInput() {
if (inputBuffer == null) {
@@ -375,74 +351,50 @@ public class Deflater {
/**
* Resets the {@code Deflater} to accept new input without affecting any
* previously made settings for the compression strategy or level. This
- * operation <i>must</i> be called after {@code finished()} returns
- * {@code true} if the {@code Deflater} is to be reused.
- *
- * @see #finished
+ * operation <i>must</i> be called after {@link #finished} returns
+ * true if the {@code Deflater} is to be reused.
*/
public synchronized void reset() {
if (streamHandle == -1) {
throw new NullPointerException();
}
-
- flushParm = NO_FLUSH;
+ flushStyle = NO_FLUSH;
finished = false;
resetImpl(streamHandle);
inputBuffer = null;
}
- private synchronized native void resetImpl(long handle);
+ private native void resetImpl(long handle);
/**
* Sets the dictionary to be used for compression by this {@code Deflater}.
- * setDictionary() can only be called if this {@code Deflater} supports the writing
- * of ZLIB headers. This is the default behaviour but can be overridden
- * using {@code Deflater(int, boolean)}.
- *
- * @param buf
- * the buffer containing the dictionary data bytes.
- * @see Deflater#Deflater(int, boolean)
+ * This method can only be called if this {@code Deflater} supports the writing
+ * of ZLIB headers. This is the default, but can be overridden
+ * using {@link #Deflater(int, boolean)}.
*/
- public void setDictionary(byte[] buf) {
- setDictionary(buf, 0, buf.length);
+ public void setDictionary(byte[] dictionary) {
+ setDictionary(dictionary, 0, dictionary.length);
}
/**
* Sets the dictionary to be used for compression by this {@code Deflater}.
- * setDictionary() can only be called if this {@code Deflater} supports the writing
- * of ZLIB headers. This is the default behaviour but can be overridden
- * using {@code Deflater(int, boolean)}.
- *
- * @param buf
- * the buffer containing the dictionary data bytes.
- * @param off
- * the offset of the data.
- * @param nbytes
- * the length of the data.
- * @see Deflater#Deflater(int, boolean)
- */
- public synchronized void setDictionary(byte[] buf, int off, int nbytes) {
+ * This method can only be called if this {@code Deflater} supports the writing
+ * of ZLIB headers. This is the default, but can be overridden
+ * using {@link #Deflater(int, boolean)}.
+ */
+ public synchronized void setDictionary(byte[] buf, int offset, int byteCount) {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- // avoid int overflow, check null buf
- if (off <= buf.length && nbytes >= 0 && off >= 0
- && buf.length - off >= nbytes) {
- setDictionaryImpl(buf, off, nbytes, streamHandle);
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
+ Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
+ setDictionaryImpl(buf, offset, byteCount, streamHandle);
}
- private synchronized native void setDictionaryImpl(byte[] buf, int off,
- int nbytes, long handle);
+ private native void setDictionaryImpl(byte[] buf, int offset, int byteCount, long handle);
/**
* Sets the input buffer the {@code Deflater} will use to extract uncompressed bytes
* for later compression.
- *
- * @param buf
- * the buffer.
*/
public void setInput(byte[] buf) {
setInput(buf, 0, buf.length);
@@ -450,89 +402,66 @@ public class Deflater {
/**
* Sets the input buffer the {@code Deflater} will use to extract uncompressed bytes
- * for later compression. Input will be taken from the buffer region
- * starting at off and ending at nbytes - 1.
- *
- * @param buf
- * the buffer containing the input data bytes.
- * @param off
- * the offset of the data.
- * @param nbytes
- * the length of the data.
- */
- public synchronized void setInput(byte[] buf, int off, int nbytes) {
+ * for later compression.
+ */
+ public synchronized void setInput(byte[] buf, int offset, int byteCount) {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- // avoid int overflow, check null buf
- if (off <= buf.length && nbytes >= 0 && off >= 0
- && buf.length - off >= nbytes) {
- inLength = nbytes;
- inRead = 0;
- if (inputBuffer == null) {
- setLevelsImpl(compressLevel, strategy, streamHandle);
- }
- inputBuffer = buf;
- setInputImpl(buf, off, nbytes, streamHandle);
- } else {
- throw new ArrayIndexOutOfBoundsException();
+ Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
+ inLength = byteCount;
+ inRead = 0;
+ if (inputBuffer == null) {
+ setLevelsImpl(compressLevel, strategy, streamHandle);
}
+ inputBuffer = buf;
+ setInputImpl(buf, offset, byteCount, streamHandle);
}
- private synchronized native void setLevelsImpl(int level, int strategy,
- long handle);
+ private native void setLevelsImpl(int level, int strategy, long handle);
- private synchronized native void setInputImpl(byte[] buf, int off,
- int nbytes, long handle);
+ private native void setInputImpl(byte[] buf, int offset, int byteCount, long handle);
/**
* Sets the compression level to be used when compressing data. The
* compression level must be a value between 0 and 9. This value must be set
- * prior to calling setInput().
- *
- * @param level
- * compression level to use
+ * prior to calling {@link #setInput}.
* @exception IllegalArgumentException
* If the compression level is invalid.
*/
public synchronized void setLevel(int level) {
if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Bad level: " + level);
}
if (inputBuffer != null) {
- throw new IllegalStateException();
+ throw new IllegalStateException("setLevel cannot be called after setInput");
}
compressLevel = level;
}
/**
* Sets the compression strategy to be used. The strategy must be one of
- * FILTERED, HUFFMAN_ONLY or DEFAULT_STRATEGY.This value must be set prior
- * to calling setInput().
+ * FILTERED, HUFFMAN_ONLY or DEFAULT_STRATEGY. This value must be set prior
+ * to calling {@link #setInput}.
*
- * @param strategy
- * compression strategy to use
* @exception IllegalArgumentException
* If the strategy specified is not one of FILTERED,
* HUFFMAN_ONLY or DEFAULT_STRATEGY.
*/
public synchronized void setStrategy(int strategy) {
if (strategy < DEFAULT_STRATEGY || strategy > HUFFMAN_ONLY) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Bad strategy: " + strategy);
}
if (inputBuffer != null) {
- throw new IllegalStateException();
+ throw new IllegalStateException("setStrategy cannot be called after setInput");
}
this.strategy = strategy;
}
/**
- * Returns a long int of total number of bytes read by the {@code Deflater}. This
- * method performs the same as {@code getTotalIn} except it returns a long value
- * instead of an integer
- *
- * @see #getTotalIn()
- * @return total number of bytes read by {@code Deflater}.
+ * Returns the total number of bytes read by the {@code Deflater}. This
+ * method is the same as {@link #getTotalIn} except that it returns a
+ * {@code long} value instead of an integer.
*/
public synchronized long getBytesRead() {
// Throw NPE here
@@ -543,12 +472,9 @@ public class Deflater {
}
/**
- * Returns a long int of total number of bytes of read by the {@code Deflater}. This
- * method performs the same as {@code getTotalOut} except it returns a long
- * value instead of an integer
- *
- * @see #getTotalOut()
- * @return bytes exactly write by {@code Deflater}
+ * Returns a the total number of bytes written by this {@code Deflater}. This
+ * method is the same as {@code getTotalOut} except it returns a
+ * {@code long} value instead of an integer.
*/
public synchronized long getBytesWritten() {
// Throw NPE here
diff --git a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
index fcbcb59..4fbddaf 100644
--- a/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
+++ b/luni/src/main/java/java/util/zip/DeflaterOutputStream.java
@@ -132,10 +132,9 @@ public class DeflaterOutputStream extends FilterOutputStream {
* If an error occurs during deflation.
*/
protected void deflate() throws IOException {
- int x = 0;
do {
- x = def.deflate(buf);
- out.write(buf, 0, x);
+ int byteCount = def.deflate(buf);
+ out.write(buf, 0, byteCount);
} while (!def.needsInput());
}
@@ -150,7 +149,7 @@ public class DeflaterOutputStream extends FilterOutputStream {
*/
@Override
public void close() throws IOException {
- // everything closed here should also be closed in ZipOuputStream.close()
+ // everything closed here should also be closed in ZipOutputStream.close()
if (!def.finished()) {
finish();
}
@@ -207,8 +206,7 @@ public class DeflaterOutputStream extends FilterOutputStream {
throw new IOException("attempt to write after finish");
}
// avoid int overflow, check null buf
- if (off <= buffer.length && nbytes >= 0 && off >= 0
- && buffer.length - off >= nbytes) {
+ if (off <= buffer.length && nbytes >= 0 && off >= 0 && buffer.length - off >= nbytes) {
if (!def.needsInput()) {
throw new IOException();
}
diff --git a/luni/src/main/java/java/util/zip/Inflater.java b/luni/src/main/java/java/util/zip/Inflater.java
index 5fb6564..8b46676 100644
--- a/luni/src/main/java/java/util/zip/Inflater.java
+++ b/luni/src/main/java/java/util/zip/Inflater.java
@@ -19,30 +19,42 @@ package java.util.zip;
import dalvik.system.CloseGuard;
import java.io.FileDescriptor;
+import java.util.Arrays;
/**
- * This class uncompresses data that was compressed using the <i>DEFLATE</i>
+ * 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>
- * Basically this class is part of the API to the stream based ZLIB compression
- * library and is used as such by {@code InflaterInputStream} and its
- * descendants.
- * <p>
- * The typical usage of a {@code Inflater} outside this package consists of a
- * specific call to one of its constructors before being passed to an instance
- * of {@code InflaterInputStream}.
*
- * @see InflaterInputStream
- * @see Deflater
+ * <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 boolean finished; // Set by the inflateImpl native
-
- int inLength;
- int inRead;
+ private int inLength;
- private boolean needsDictionary; // Set by the inflateImpl native
+ private int inRead; // Set by inflateImpl.
+ private boolean finished; // Set by inflateImpl.
+ private boolean needsDictionary; // Set by inflateImpl.
private long streamHandle = -1;
@@ -50,7 +62,7 @@ public class Inflater {
/**
* This constructor creates an inflater that expects a header from the input
- * stream. Use {@code Inflater(boolean)} if the input comes without a ZLIB
+ * stream. Use {@link #Inflater(boolean)} if the input comes without a ZLIB
* header.
*/
public Inflater() {
@@ -73,8 +85,10 @@ public class Inflater {
private native long createStream(boolean noHeader1);
/**
- * Release any resources associated with this {@code Inflater}. Any unused
- * input/output is discarded. This is also called by the finalize method.
+ * 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();
@@ -86,7 +100,7 @@ public class Inflater {
}
}
- private native synchronized void endImpl(long handle);
+ private native void endImpl(long handle);
@Override protected void finalize() {
try {
@@ -105,7 +119,7 @@ public class Inflater {
/**
* Indicates if the {@code Inflater} has inflated the entire deflated
- * stream. If deflated bytes remain and {@code needsInput()} returns {@code
+ * 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}.
*
@@ -117,11 +131,8 @@ public class Inflater {
}
/**
- * Returns the <i>Adler32</i> checksum of either all bytes inflated, or the
- * checksum of the preset dictionary if one has been supplied.
- *
- * @return The <i>Adler32</i> checksum associated with this
- * {@code Inflater}.
+ * 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() {
if (streamHandle == -1) {
@@ -130,14 +141,12 @@ public class Inflater {
return getAdlerImpl(streamHandle);
}
- private native synchronized int getAdlerImpl(long handle);
+ private native int getAdlerImpl(long handle);
/**
* Returns the total number of bytes read by the {@code Inflater}. This
- * method performs the same as {@code getTotalIn()} except that it returns a
+ * method is the same as {@link #getTotalIn} except that it returns a
* {@code long} value instead of an integer.
- *
- * @return the total number of bytes read.
*/
public synchronized long getBytesRead() {
// Throw NPE here
@@ -148,11 +157,9 @@ public class Inflater {
}
/**
- * Returns a the total number of bytes read by the {@code Inflater}. This
- * method performs the same as {@code getTotalOut} except it returns a
+ * 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.
- *
- * @return the total bytes written to the output buffer.
*/
public synchronized long getBytesWritten() {
// Throw NPE here
@@ -163,51 +170,41 @@ public class Inflater {
}
/**
- * Returns the number of bytes of current input remaining to be read by the
+ * Returns the number of bytes of current input remaining to be read by this
* inflater.
- *
- * @return the number of bytes of unread input.
*/
public synchronized int getRemaining() {
return inLength - inRead;
}
/**
- * Returns total number of bytes of input read by the {@code Inflater}. The
- * result value is limited by {@code Integer.MAX_VALUE}.
- *
- * @return the total number of bytes read.
+ * 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() {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- long totalIn = getTotalInImpl(streamHandle);
- return (totalIn <= Integer.MAX_VALUE ? (int) totalIn
- : Integer.MAX_VALUE);
+ return (int) Math.min(getTotalInImpl(streamHandle), (long) Integer.MAX_VALUE);
}
- private synchronized native long getTotalInImpl(long handle);
+ private native long getTotalInImpl(long handle);
/**
- * Returns total number of bytes written to the output buffer by the {@code
- * Inflater}. The result value is limited by {@code Integer.MAX_VALUE}.
- *
- * @return the total bytes of output data written.
+ * 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() {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- long totalOut = getTotalOutImpl(streamHandle);
- return (totalOut <= Integer.MAX_VALUE ? (int) totalOut
- : Integer.MAX_VALUE);
+ return (int) Math.min(getTotalOutImpl(streamHandle), (long) Integer.MAX_VALUE);
}
- private native synchronized long getTotalOutImpl(long handle);
+ private native long getTotalOutImpl(long handle);
/**
- * Inflates bytes from current input and stores them in {@code buf}.
+ * Inflates bytes from the current input and stores them in {@code buf}.
*
* @param buf
* the buffer where decompressed data bytes are written.
@@ -221,29 +218,18 @@ public class Inflater {
}
/**
- * Inflates up to n bytes from the current input and stores them in {@code
- * buf} starting at {@code off}.
+ * Inflates up to {@code byteCount} bytes from the current input and stores them in
+ * {@code buf} starting at {@code offset}.
*
- * @param buf
- * the buffer to write inflated bytes to.
- * @param off
- * the offset in buffer where to start writing decompressed data.
- * @param nbytes
- * the number of inflated bytes to write to {@code buf}.
* @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 off, int nbytes)
- throws DataFormatException {
- // avoid int overflow, check null buf
- if (off > buf.length || nbytes < 0 || off < 0
- || buf.length - off < nbytes) {
- throw new ArrayIndexOutOfBoundsException();
- }
+ public synchronized int inflate(byte[] buf, int offset, int byteCount) throws DataFormatException {
+ Arrays.checkOffsetAndCount(buf.length, offset, byteCount);
- if (nbytes == 0) {
+ if (byteCount == 0) {
return 0;
}
@@ -257,45 +243,35 @@ public class Inflater {
boolean neededDict = needsDictionary;
needsDictionary = false;
- int result = inflateImpl(buf, off, nbytes, streamHandle);
+ int result = inflateImpl(buf, offset, byteCount, streamHandle);
if (needsDictionary && neededDict) {
throw new DataFormatException("Needs dictionary");
}
-
return result;
}
- private native synchronized int inflateImpl(byte[] buf, int off,
- int nbytes, long handle);
+ private native int inflateImpl(byte[] buf, int offset, int byteCount, long handle);
/**
- * Indicates whether the input bytes were compressed with a preset
- * dictionary. This method should be called prior to {@code inflate()} to
- * determine whether a dictionary is required. If so {@code setDictionary()}
- * should be called with the appropriate dictionary prior to calling {@code
- * inflate()}.
- *
- * @return {@code true} if a preset dictionary is required for inflation.
- * @see #setDictionary(byte[])
- * @see #setDictionary(byte[], int, int)
+ * 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;
}
/**
- * Indicates that input has to be passed to the inflater.
- *
- * @return {@code true} if {@code setInput} has to be called before
- * inflation can proceed.
- * @see #setInput(byte[])
+ * Returns true if {@link #setInput} must be called before inflation can continue.
*/
public synchronized boolean needsInput() {
return inRead == inLength;
}
/**
- * Resets the {@code Inflater}. Should be called prior to inflating a new
+ * Resets this {@code Inflater}. Should be called prior to inflating a new
* set of data.
*/
public synchronized void reset() {
@@ -308,123 +284,65 @@ public class Inflater {
resetImpl(streamHandle);
}
- private native synchronized void resetImpl(long handle);
+ private native void resetImpl(long handle);
/**
- * Sets the preset dictionary to be used for inflation to {@code buf}.
- * {@code needsDictionary()} can be called to determine whether the current
- * input was deflated using a preset dictionary.
- *
- * @param buf
- * The buffer containing the dictionary bytes.
- * @see #needsDictionary
+ * Sets the preset dictionary to be used for inflation to {@code dictionary}.
+ * See {@link #needsDictionary} for details.
*/
- public synchronized void setDictionary(byte[] buf) {
- setDictionary(buf, 0, buf.length);
+ public synchronized void setDictionary(byte[] dictionary) {
+ setDictionary(dictionary, 0, dictionary.length);
}
/**
- * Like {@code setDictionary(byte[])}, allowing to define a specific region
- * inside {@code buf} to be used as a dictionary.
- * <p>
- * The dictionary should be set if the {@link #inflate(byte[])} returned
- * zero bytes inflated and {@link #needsDictionary()} returns
- * <code>true</code>.
- *
- * @param buf
- * the buffer containing the dictionary data bytes.
- * @param off
- * the offset of the data.
- * @param nbytes
- * the length of the data.
- * @see #needsDictionary
+ * 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[] buf, int off, int nbytes) {
+ public synchronized void setDictionary(byte[] dictionary, int offset, int byteCount) {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- // avoid int overflow, check null buf
- if (off <= buf.length && nbytes >= 0 && off >= 0
- && buf.length - off >= nbytes) {
- setDictionaryImpl(buf, off, nbytes, streamHandle);
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
+ Arrays.checkOffsetAndCount(dictionary.length, offset, byteCount);
+ setDictionaryImpl(dictionary, offset, byteCount, streamHandle);
}
- private native synchronized void setDictionaryImpl(byte[] buf, int off,
- int nbytes, long handle);
+ private native void setDictionaryImpl(byte[] dictionary, int offset, int byteCount, long handle);
/**
- * Sets the current input to to be decrompressed. This method should only be
- * called if {@code needsInput()} returns {@code true}.
- *
- * @param buf
- * the input buffer.
- * @see #needsInput
+ * 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 the region of the input buffer starting at
- * {@code off} and ending at {@code nbytes - 1} where data is written after
- * decompression. This method should only be called if {@code needsInput()}
- * returns {@code true}.
- *
- * @param buf
- * the input buffer.
- * @param off
- * the offset to read from the input buffer.
- * @param nbytes
- * the number of bytes to read.
- * @see #needsInput
+ * 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 off, int nbytes) {
+ public synchronized void setInput(byte[] buf, int offset, int byteCount) {
if (streamHandle == -1) {
throw new IllegalStateException();
}
- // avoid int overflow, check null buf
- if (off <= buf.length && nbytes >= 0 && off >= 0
- && buf.length - off >= nbytes) {
- inRead = 0;
- inLength = nbytes;
- setInputImpl(buf, off, nbytes, streamHandle);
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
+ 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);
+
// BEGIN android-only
- /**
- * Sets the current input to the region within a file starting at {@code
- * off} and ending at {@code nbytes - 1}. This method should only be called
- * if {@code needsInput()} returns {@code true}.
- *
- * @param fd
- * the input file.
- * @param off
- * the offset to read from in buffer.
- * @param nbytes
- * the number of bytes to read.
- * @see #needsInput
- */
- synchronized int setFileInput(FileDescriptor fd, long off, int nbytes) {
+ synchronized int setFileInput(FileDescriptor fd, long offset, int byteCount) {
if (streamHandle == -1) {
throw new IllegalStateException();
}
inRead = 0;
- inLength = setFileInputImpl(fd, off, nbytes, streamHandle);
+ inLength = setFileInputImpl(fd, offset, byteCount, streamHandle);
return inLength;
}
- // END android-only
- private native synchronized void setInputImpl(byte[] buf, int off,
- int nbytes, long handle);
-
- // BEGIN android-only
- private native synchronized int setFileInputImpl(FileDescriptor fd, long off,
- int nbytes, long handle);
+ private native int setFileInputImpl(FileDescriptor fd, long offset, int byteCount, long handle);
// END android-only
}
diff --git a/luni/src/main/java/java/util/zip/InflaterInputStream.java b/luni/src/main/java/java/util/zip/InflaterInputStream.java
index 94c195c..fd532cc 100644
--- a/luni/src/main/java/java/util/zip/InflaterInputStream.java
+++ b/luni/src/main/java/java/util/zip/InflaterInputStream.java
@@ -168,8 +168,7 @@ public class InflaterInputStream extends FilterInputStream {
}
// avoid int overflow, check null buffer
- if (off > buffer.length || nbytes < 0 || off < 0
- || buffer.length - off < nbytes) {
+ if (off > buffer.length || nbytes < 0 || off < 0 || buffer.length - off < nbytes) {
throw new ArrayIndexOutOfBoundsException();
}
diff --git a/luni/src/main/java/java/util/zip/ZipInputStream.java b/luni/src/main/java/java/util/zip/ZipInputStream.java
index bb9f20c..9c424f4 100644
--- a/luni/src/main/java/java/util/zip/ZipInputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipInputStream.java
@@ -72,11 +72,7 @@ import org.apache.harmony.luni.platform.OSMemory;
* @see ZipFile
*/
public class ZipInputStream extends InflaterInputStream implements ZipConstants {
- static final int DEFLATED = 8;
-
- static final int STORED = 0;
-
- static final int ZIPLocalHeaderVersionNeeded = 20;
+ private static final int ZIPLocalHeaderVersionNeeded = 20;
private boolean entriesEnd = false;
@@ -86,7 +82,7 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
private int inRead, lastRead = 0;
- ZipEntry currentEntry;
+ private ZipEntry currentEntry;
private final byte[] hdrBuf = new byte[LOCHDR - LOCVER];
@@ -159,7 +155,7 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
}
int inB, out;
- if (currentEntry.compressionMethod == DEFLATED) {
+ if (currentEntry.compressionMethod == ZipEntry.DEFLATED) {
inB = inf.getTotalIn();
out = inf.getTotalOut();
} else {
@@ -313,7 +309,7 @@ public class ZipInputStream extends InflaterInputStream implements ZipConstants
return -1;
}
- if (currentEntry.compressionMethod == STORED) {
+ if (currentEntry.compressionMethod == ZipEntry.STORED) {
int csize = (int) currentEntry.size;
if (inRead >= csize) {
return -1;
diff --git a/luni/src/main/java/java/util/zip/ZipOutputStream.java b/luni/src/main/java/java/util/zip/ZipOutputStream.java
index 067fc43..2cf80be 100644
--- a/luni/src/main/java/java/util/zip/ZipOutputStream.java
+++ b/luni/src/main/java/java/util/zip/ZipOutputStream.java
@@ -20,6 +20,7 @@ package java.util.zip;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.charset.Charsets;
import java.util.Vector;
/**
@@ -72,7 +73,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
*/
public static final int STORED = 0;
- static final int ZIPLocalHeaderVersionNeeded = 20;
+ private static final int ZIPLocalHeaderVersionNeeded = 20;
private String comment;
@@ -245,7 +246,6 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
// Write the central dir
out.write(cDir.toByteArray());
cDir = null;
-
}
/**
@@ -260,7 +260,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
* If an error occurs storing the entry.
* @see #write
*/
- public void putNextEntry(ZipEntry ze) throws java.io.IOException {
+ public void putNextEntry(ZipEntry ze) throws IOException {
if (currentEntry != null) {
closeEntry();
}
@@ -279,7 +279,8 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
if (entries.contains(ze.name)) {
throw new ZipException("Entry already exists: " + ze.name);
}
- nameLength = utf8Count(ze.name);
+ nameBytes = ze.name.getBytes(Charsets.UTF_8);
+ nameLength = nameBytes.length;
if (nameLength > 0xffff) {
throw new IllegalArgumentException("Name too long: " + nameLength + " UTF-8 bytes");
}
@@ -328,7 +329,6 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
} else {
writeShort(out, 0);
}
- nameBytes = toUTF8Bytes(currentEntry.name, nameLength);
out.write(nameBytes);
if (currentEntry.extra != null) {
out.write(currentEntry.extra);
@@ -358,8 +358,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
* @see Deflater
*/
public void setLevel(int level) {
- if (level < Deflater.DEFAULT_COMPRESSION
- || level > Deflater.BEST_COMPRESSION) {
+ if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {
throw new IllegalArgumentException();
}
compressLevel = level;
@@ -378,10 +377,9 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
throw new IllegalArgumentException();
}
compressMethod = method;
-
}
- private long writeLong(OutputStream os, long i) throws java.io.IOException {
+ private long writeLong(OutputStream os, long i) throws IOException {
// Write out the long value as an unsigned int
os.write((int) (i & 0xFF));
os.write((int) (i >> 8) & 0xFF);
@@ -390,11 +388,10 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
return i;
}
- private int writeShort(OutputStream os, int i) throws java.io.IOException {
+ private int writeShort(OutputStream os, int i) throws IOException {
os.write(i & 0xFF);
os.write((i >> 8) & 0xFF);
return i;
-
}
/**
@@ -404,8 +401,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
* If an error occurs writing to the stream
*/
@Override
- public void write(byte[] buffer, int off, int nbytes)
- throws java.io.IOException {
+ public void write(byte[] buffer, int off, int nbytes) throws IOException {
// avoid int overflow, check null buf
if ((off < 0 || (nbytes < 0) || off > buffer.length)
|| (buffer.length - off < nbytes)) {
@@ -424,40 +420,6 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant
crc.update(buffer, off, nbytes);
}
- static int utf8Count(String value) {
- int total = 0;
- for (int i = value.length(); --i >= 0;) {
- char ch = value.charAt(i);
- if (ch < 0x80) {
- total++;
- } else if (ch < 0x800) {
- total += 2;
- } else {
- total += 3;
- }
- }
- return total;
- }
-
- static byte[] toUTF8Bytes(String value, int length) {
- byte[] result = new byte[length];
- int pos = result.length;
- for (int i = value.length(); --i >= 0;) {
- char ch = value.charAt(i);
- if (ch < 0x80) {
- result[--pos] = (byte) ch;
- } else if (ch < 0x800) {
- result[--pos] = (byte) (0x80 | (ch & 0x3f));
- result[--pos] = (byte) (0xc0 | (ch >> 6));
- } else {
- result[--pos] = (byte) (0x80 | (ch & 0x3f));
- result[--pos] = (byte) (0x80 | ((ch >> 6) & 0x3f));
- result[--pos] = (byte) (0xe0 | (ch >> 12));
- }
- }
- return result;
- }
-
private void checkClosed() throws IOException {
if (cDir == null) {
throw new IOException("Stream is closed");
diff --git a/luni/src/main/native/java_util_zip_Deflater.cpp b/luni/src/main/native/java_util_zip_Deflater.cpp
index c0b2fef..4631fa9 100644
--- a/luni/src/main/native/java_util_zip_Deflater.cpp
+++ b/luni/src/main/native/java_util_zip_Deflater.cpp
@@ -21,11 +21,6 @@
#include "ScopedPrimitiveArray.h"
#include "zip.h"
-static struct {
- jfieldID inRead;
- jfieldID finished;
-} gCachedFields;
-
static void Deflater_setDictionaryImpl(JNIEnv* env, jobject, jbyteArray dict, int off, int len, jlong handle) {
toNativeZipStream(handle)->setDictionary(env, dict, off, len, false);
}
@@ -42,7 +37,6 @@ static jint Deflater_getAdlerImpl(JNIEnv*, jobject, jlong handle) {
return toNativeZipStream(handle)->stream.adler;
}
-/* Create a new stream . This stream cannot be used until it has been properly initialized. */
static jlong Deflater_createStream(JNIEnv * env, jobject, jint level, jint strategy, jboolean noHeader) {
UniquePtr<NativeZipStream> jstream(new NativeZipStream);
if (jstream.get() == NULL) {
@@ -50,17 +44,19 @@ static jlong Deflater_createStream(JNIEnv * env, jobject, jint level, jint strat
return -1;
}
- int wbits = 12; // Was 15, made it 12 to reduce memory consumption. Use MAX for fastest.
- int mlevel = 5; // Was 9, made it 5 to reduce memory consumption. Might result
- // in out-of-memory problems according to some web pages. The
- // ZLIB docs are a bit vague, unfortunately. The default
- // results in 2 x 128K being allocated per Deflater, which is
- // not acceptable.
- /*Unable to find official doc that this is the way to avoid zlib header use. However doc in zipsup.c claims it is so */
- if (noHeader) {
- wbits = wbits / -1;
- }
- int err = deflateInit2(&jstream->stream, level, Z_DEFLATED, wbits, mlevel, strategy);
+ /*
+ * See zlib.h for documentation of the deflateInit2 windowBits and memLevel parameters.
+ *
+ * zconf.h says the "requirements for deflate are (in bytes):
+ * (1 << (windowBits+2)) + (1 << (memLevel+9))
+ * that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values)
+ * plus a few kilobytes for small objects."
+ */
+ // TODO: should we just use DEF_WBITS (15) and DEF_MEM_LEVEL (8) now?
+ int windowBits = noHeader ? -12 : 12; // Was 15, made it 12 to reduce memory consumption. Use MAX_WBITS for fastest.
+ int memLevel = 5; // Was 9 (MAX_MEM_LEVEL), made it 5 to reduce memory consumption. Might result
+ // in out-of-memory problems according to some web pages.
+ int err = deflateInit2(&jstream->stream, level, Z_DEFLATED, windowBits, memLevel, strategy);
if (err != Z_OK) {
throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err);
return -1;
@@ -72,35 +68,45 @@ static void Deflater_setInputImpl(JNIEnv* env, jobject, jbyteArray buf, jint off
toNativeZipStream(handle)->setInput(env, buf, off, len);
}
-static jint Deflater_deflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int off, int len, jlong handle, int flushParm) {
- /* We need to get the number of bytes already read */
- jint inBytes = env->GetIntField(recv, gCachedFields.inRead);
-
+static jint Deflater_deflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int off, int len, jlong handle, int flushStyle) {
NativeZipStream* stream = toNativeZipStream(handle);
- stream->stream.avail_out = len;
- jint sin = stream->stream.total_in;
- jint sout = stream->stream.total_out;
ScopedByteArrayRW out(env, buf);
if (out.get() == NULL) {
return -1;
}
stream->stream.next_out = reinterpret_cast<Bytef*>(out.get() + off);
- int err = deflate(&stream->stream, flushParm);
- if (err != Z_OK) {
- if (err == Z_MEM_ERROR) {
- jniThrowOutOfMemoryError(env, NULL);
- return 0;
- }
- if (err == Z_STREAM_END) {
- env->SetBooleanField(recv, gCachedFields.finished, JNI_TRUE);
- return stream->stream.total_out - sout;
+ stream->stream.avail_out = len;
+
+ // TODO: make this a bit more like Inflater.cpp, using the pointers rather than the ints.
+ jint initialTotalIn = stream->stream.total_in;
+ jint initialTotalOut = stream->stream.total_out;
+
+ int err = deflate(&stream->stream, flushStyle);
+ switch (err) {
+ case Z_OK:
+ // TODO: does this really have to be conditional and in here? can we be more like Inflater?
+ if (flushStyle != Z_FINISH) {
+ static jfieldID inReadField = env->GetFieldID(JniConstants::deflaterClass, "inRead", "I");
+ jint inReadValue = env->GetIntField(recv, inReadField);
+ inReadValue += (stream->stream.total_in - initialTotalIn);
+ env->SetIntField(recv, inReadField, inReadValue);
}
+ break;
+ case Z_STREAM_END:
+ static jfieldID finished = env->GetFieldID(JniConstants::deflaterClass, "finished", "Z");
+ env->SetBooleanField(recv, finished, JNI_TRUE);
+ break;
+ case Z_BUF_ERROR:
+ // zlib reports this "if no progress is possible (for example avail_in or avail_out was
+ // zero) ... Z_BUF_ERROR is not fatal, and deflate() can be called again with more
+ // input and more output space to continue compressing".
+ break;
+ default:
+ throwExceptionForZlibError(env, "java/util/zip/DataFormatException", err);
+ return -1;
}
- if (flushParm != Z_FINISH) {
- /* Need to update the number of input bytes read. */
- env->SetIntField(recv, gCachedFields.inRead, (jint) stream->stream.total_in - sin + inBytes);
- }
- return stream->stream.total_out - sout;
+
+ return stream->stream.total_out - initialTotalOut;
}
static void Deflater_endImpl(JNIEnv*, jobject, jlong handle) {
@@ -144,7 +150,5 @@ static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Deflater, setLevelsImpl, "(IIJ)V"),
};
int register_java_util_zip_Deflater(JNIEnv* env) {
- gCachedFields.finished = env->GetFieldID(JniConstants::deflaterClass, "finished", "Z");
- gCachedFields.inRead = env->GetFieldID(JniConstants::deflaterClass, "inRead", "I");
return jniRegisterNativeMethods(env, "java/util/zip/Deflater", gMethods, NELEM(gMethods));
}
diff --git a/luni/src/main/native/java_util_zip_Inflater.cpp b/luni/src/main/native/java_util_zip_Inflater.cpp
index 8dd9e87..192d1b5 100644
--- a/luni/src/main/native/java_util_zip_Inflater.cpp
+++ b/luni/src/main/native/java_util_zip_Inflater.cpp
@@ -22,13 +22,6 @@
#include "zip.h"
#include <errno.h>
-static struct {
- jfieldID inRead;
- jfieldID finished;
- jfieldID needsDictionary;
-} gCachedFields;
-
-/* Create a new stream . This stream cannot be used until it has been properly initialized. */
static jlong Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) {
UniquePtr<NativeZipStream> jstream(new NativeZipStream);
if (jstream.get() == NULL) {
@@ -38,15 +31,14 @@ static jlong Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) {
jstream->stream.adler = 1;
/*
- * In the range 8..15 for checked, or -8..-15 for unchecked inflate. Unchecked
- * is appropriate for formats like zip that do their own validity checking.
+ * See zlib.h for documentation of the inflateInit2 windowBits parameter.
+ *
+ * zconf.h says the "requirements for inflate are (in bytes) 1 << windowBits
+ * that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ * for small objects." This means that we can happily use the default
+ * here without worrying about memory consumption.
*/
- /* Window bits to use. 15 is fastest but consumes the most memory */
- int wbits = 15; /*Use MAX for fastest */
- if (noHeader) {
- wbits = wbits / -1;
- }
- int err = inflateInit2(&jstream->stream, wbits);
+ int err = inflateInit2(&jstream->stream, noHeader ? -DEF_WBITS : DEF_WBITS);
if (err != Z_OK) {
throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err);
return -1;
@@ -95,46 +87,44 @@ static jint Inflater_setFileInputImpl(JNIEnv* env, jobject, jobject javaFileDesc
}
static jint Inflater_inflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int off, int len, jlong handle) {
- jfieldID fid2 = 0;
-
- /* We need to get the number of bytes already read */
- jfieldID fid = gCachedFields.inRead;
- jint inBytes = env->GetIntField(recv, fid);
-
NativeZipStream* stream = toNativeZipStream(handle);
- stream->stream.avail_out = len;
- jint sin = stream->stream.total_in;
- jint sout = stream->stream.total_out;
ScopedByteArrayRW out(env, buf);
if (out.get() == NULL) {
return -1;
}
stream->stream.next_out = reinterpret_cast<Bytef*>(out.get() + off);
+ stream->stream.avail_out = len;
+
+ Bytef* initialNextIn = stream->stream.next_in;
+ Bytef* initialNextOut = stream->stream.next_out;
+
int err = inflate(&stream->stream, Z_SYNC_FLUSH);
- if (err != Z_OK) {
- if (err == Z_STREAM_ERROR) {
- return 0;
- }
- if (err == Z_STREAM_END || err == Z_NEED_DICT) {
- env->SetIntField(recv, fid, (jint) stream->stream.total_in - sin + inBytes);
- if (err == Z_STREAM_END) {
- fid2 = gCachedFields.finished;
- } else {
- fid2 = gCachedFields.needsDictionary;
- }
- env->SetBooleanField(recv, fid2, JNI_TRUE);
- return stream->stream.total_out - sout;
- } else {
- throwExceptionForZlibError(env, "java/util/zip/DataFormatException", err);
- return -1;
- }
+ switch (err) {
+ case Z_OK:
+ break;
+ case Z_NEED_DICT:
+ static jfieldID needsDictionary = env->GetFieldID(JniConstants::inflaterClass, "needsDictionary", "Z");
+ env->SetBooleanField(recv, needsDictionary, JNI_TRUE);
+ break;
+ case Z_STREAM_END:
+ static jfieldID finished = env->GetFieldID(JniConstants::inflaterClass, "finished", "Z");
+ env->SetBooleanField(recv, finished, JNI_TRUE);
+ break;
+ case Z_STREAM_ERROR:
+ return 0;
+ default:
+ throwExceptionForZlibError(env, "java/util/zip/DataFormatException", err);
+ return -1;
}
- /* Need to update the number of input bytes read. Is there a better way
- * (Maybe global the fid then delete when end is called)?
- */
- env->SetIntField(recv, fid, (jint) stream->stream.total_in - sin + inBytes);
- return stream->stream.total_out - sout;
+ jint bytesRead = stream->stream.next_in - initialNextIn;
+ jint bytesWritten = stream->stream.next_out - initialNextOut;
+
+ static jfieldID inReadField = env->GetFieldID(JniConstants::inflaterClass, "inRead", "I");
+ jint inReadValue = env->GetIntField(recv, inReadField);
+ inReadValue += bytesRead;
+ env->SetIntField(recv, inReadField, inReadValue);
+ return bytesWritten;
}
static jint Inflater_getAdlerImpl(JNIEnv*, jobject, jlong handle) {
@@ -179,8 +169,5 @@ static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Inflater, setInputImpl, "([BIIJ)V"),
};
int register_java_util_zip_Inflater(JNIEnv* env) {
- gCachedFields.finished = env->GetFieldID(JniConstants::inflaterClass, "finished", "Z");
- gCachedFields.inRead = env->GetFieldID(JniConstants::inflaterClass, "inRead", "I");
- gCachedFields.needsDictionary = env->GetFieldID(JniConstants::inflaterClass, "needsDictionary", "Z");
return jniRegisterNativeMethods(env, "java/util/zip/Inflater", gMethods, NELEM(gMethods));
}
diff --git a/luni/src/main/native/zip.h b/luni/src/main/native/zip.h
index 9909d44..0f3c0c1 100644
--- a/luni/src/main/native/zip.h
+++ b/luni/src/main/native/zip.h
@@ -23,6 +23,7 @@
#include "UniquePtr.h"
#include "jni.h"
#include "zlib.h"
+#include "zutil.h"
static void throwExceptionForZlibError(JNIEnv* env, const char* exceptionClassName, int error) {
if (error == Z_MEM_ERROR) {
diff --git a/luni/src/test/java/libcore/java/util/zip/DeflaterOutputStreamTest.java b/luni/src/test/java/libcore/java/util/zip/DeflaterOutputStreamTest.java
index 55144ed..95e6891 100644
--- a/luni/src/test/java/libcore/java/util/zip/DeflaterOutputStreamTest.java
+++ b/luni/src/test/java/libcore/java/util/zip/DeflaterOutputStreamTest.java
@@ -57,8 +57,7 @@ public class DeflaterOutputStreamTest extends TestCase {
* way demonstrate that data is unavailable. Ie. other techniques will cause
* the dry read to block indefinitely.
*/
- private InflaterInputStream createInflaterStream(final boolean flushing)
- throws Exception {
+ private InflaterInputStream createInflaterStream(final boolean flushing) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
final PipedOutputStream pout = new PipedOutputStream();
PipedInputStream pin = new PipedInputStream(pout);
diff --git a/luni/src/test/java/libcore/java/util/zip/InflaterTest.java b/luni/src/test/java/libcore/java/util/zip/InflaterTest.java
new file mode 100644
index 0000000..1021cb0
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/zip/InflaterTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed 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 libcore.java.util.zip;
+
+import java.io.ByteArrayOutputStream;
+import java.util.zip.Adler32;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+import junit.framework.TestCase;
+
+public class InflaterTest extends TestCase {
+ public void testDefaultDictionary() throws Exception {
+ assertRoundTrip(null);
+ }
+
+ public void testPresetCustomDictionary() throws Exception {
+ assertRoundTrip("expected".getBytes("UTF-8"));
+ }
+
+ private static void assertRoundTrip(byte[] dictionary) throws Exception {
+ // Construct a nice long input byte sequence.
+ String expected = makeString();
+ byte[] expectedBytes = expected.getBytes("UTF-8");
+
+ // Compress the bytes, using the passed-in dictionary (or no dictionary).
+ byte[] deflatedBytes = deflate(expectedBytes, dictionary);
+
+ // Get ready to decompress the bytes again...
+ Inflater inflater = new Inflater();
+ // We'll only supply the input a little bit at a time, so that zlib has to ask for more.
+ final int CHUNK_SIZE = 16;
+ int offset = 0;
+ inflater.setInput(deflatedBytes, offset, CHUNK_SIZE);
+ offset += CHUNK_SIZE;
+ // If we used a dictionary to compress, check that we're asked for that same dictionary.
+ if (dictionary != null) {
+ // 1. there's no data available immediately...
+ assertEquals(0, inflater.inflate(new byte[8]));
+ // 2. ...because you need a dictionary.
+ assertTrue(inflater.needsDictionary());
+ // 3. ...and that dictionary has the same Adler32 as the dictionary we used.
+ assertEquals(adler32(dictionary), inflater.getAdler());
+ inflater.setDictionary(dictionary);
+ }
+ // Do the actual decompression, now the dictionary's set up appropriately...
+ // We use a tiny output buffer to ensure that we call inflate multiple times, and
+ // a tiny input buffer to ensure that zlib has to ask for more input.
+ ByteArrayOutputStream inflatedBytes = new ByteArrayOutputStream();
+ byte[] buf = new byte[8];
+ while (inflatedBytes.size() != expectedBytes.length) {
+ if (inflater.needsInput()) {
+ int nextChunkByteCount = Math.min(CHUNK_SIZE, deflatedBytes.length - offset);
+ inflater.setInput(deflatedBytes, offset, nextChunkByteCount);
+ offset += nextChunkByteCount;
+ } else {
+ int inflatedByteCount = inflater.inflate(buf);
+ if (inflatedByteCount > 0) {
+ inflatedBytes.write(buf, 0, inflatedByteCount);
+ }
+ }
+ }
+ inflater.end();
+
+ assertEquals(expected, new String(inflatedBytes.toByteArray(), "UTF-8"));
+ }
+
+ private static String makeString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 1024; ++i) {
+ sb.append(i + 1024);
+ }
+ return sb.toString();
+ }
+
+ private static byte[] deflate(byte[] input, byte[] dictionary) {
+ Deflater deflater = new Deflater();
+ if (dictionary != null) {
+ deflater.setDictionary(dictionary);
+ }
+ deflater.setInput(input);
+ deflater.finish();
+ ByteArrayOutputStream deflatedBytes = new ByteArrayOutputStream();
+ byte[] buf = new byte[8];
+ while (!deflater.finished()) {
+ int byteCount = deflater.deflate(buf);
+ deflatedBytes.write(buf, 0, byteCount);
+ }
+ deflater.end();
+ return deflatedBytes.toByteArray();
+ }
+
+ private static int adler32(byte[] bytes) {
+ Adler32 adler32 = new Adler32();
+ adler32.update(bytes);
+ return (int) adler32.getValue();
+ }
+}