diff options
author | Elliott Hughes <enh@google.com> | 2010-12-03 10:59:21 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-12-03 10:59:21 -0800 |
commit | 7bf1b45888a5e48919abc720942be648ea3c6b1a (patch) | |
tree | 11d6bd29dfb261ea7f75fcd9641b5f3c76b7ef2a | |
parent | 285800878fd5f733290e1085b958c94e7b50bf5b (diff) | |
parent | 2d9c5fa8ce0182cd8c14736241b709fd50cab6f8 (diff) | |
download | libcore-7bf1b45888a5e48919abc720942be648ea3c6b1a.zip libcore-7bf1b45888a5e48919abc720942be648ea3c6b1a.tar.gz libcore-7bf1b45888a5e48919abc720942be648ea3c6b1a.tar.bz2 |
Merge "Slight cleanup of java.util.zip code, plus a bug fix." into dalvik-dev
-rw-r--r-- | luni/src/main/java/java/util/Arrays.java | 13 | ||||
-rw-r--r-- | luni/src/main/java/java/util/zip/Deflater.java | 310 | ||||
-rw-r--r-- | luni/src/main/java/java/util/zip/DeflaterOutputStream.java | 10 | ||||
-rw-r--r-- | luni/src/main/java/java/util/zip/Inflater.java | 264 | ||||
-rw-r--r-- | luni/src/main/java/java/util/zip/InflaterInputStream.java | 3 | ||||
-rw-r--r-- | luni/src/main/java/java/util/zip/ZipInputStream.java | 12 | ||||
-rw-r--r-- | luni/src/main/java/java/util/zip/ZipOutputStream.java | 56 | ||||
-rw-r--r-- | luni/src/main/native/java_util_zip_Deflater.cpp | 84 | ||||
-rw-r--r-- | luni/src/main/native/java_util_zip_Inflater.cpp | 85 | ||||
-rw-r--r-- | luni/src/main/native/zip.h | 1 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/util/zip/DeflaterOutputStreamTest.java | 3 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/util/zip/InflaterTest.java | 111 |
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 — 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(); + } +} |