/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.util.zip; import dalvik.system.CloseGuard; import java.util.Arrays; import libcore.util.EmptyArray; /** * This class compresses data using the <i>DEFLATE</i> algorithm (see <a * href="http://www.gzip.org/algorithm.txt">specification</a>). * * <p>It is usually more convenient to use {@link 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 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. * * <a name="compression_level"></a><h3>Compression levels</h3> * <p>A compression level must be {@link #DEFAULT_COMPRESSION} to compromise between speed and * compression (currently equivalent to level 6), or between 0 ({@link #NO_COMPRESSION}, where * the input is simply copied) and 9 ({@link #BEST_COMPRESSION}). Level 1 ({@link #BEST_SPEED}) * performs some compression, but with minimal speed overhead. */ public class Deflater { /** * This <a href="#compression_level">compression level</a> gives the best compression, * but takes the most time. */ public static final int BEST_COMPRESSION = 9; /** * This <a href="#compression_level">compression level</a> gives minimal compression, * but takes the least time (of any level that actually performs compression; * see {@link #NO_COMPRESSION}). */ public static final int BEST_SPEED = 1; /** * This <a href="#compression_level">compression level</a> does no compression. * It is even faster than {@link #BEST_SPEED}. */ public static final int NO_COMPRESSION = 0; /** * The default <a href="#compression_level">compression level</a>. * This is a trade-off between speed and compression, currently equivalent to level 6. */ public static final int DEFAULT_COMPRESSION = -1; /** * The default compression strategy. */ public static final int DEFAULT_STRATEGY = 0; /** * The default compression method. */ public static final int DEFLATED = 8; /** * A compression strategy. */ public static final int FILTERED = 1; /** * A compression strategy. */ public static final int HUFFMAN_ONLY = 2; /** * Use buffering for best compression. * @since 1.7 */ public static final int NO_FLUSH = 0; /** * Flush buffers so recipients can immediately decode the data sent thus * far. This mode may degrade compression. * @since 1.7 */ public static final int SYNC_FLUSH = 2; /** * Flush buffers so recipients can immediately decode the data sent thus * far. The compression state is also reset to permit random access and * recovery for clients who have discarded or damaged their own copy. This * mode may degrade compression. * @since 1.7 */ public static final int FULL_FLUSH = 3; /** * Flush buffers and mark the end of the data stream. */ private static final int FINISH = 4; /** * The ugly name flushParm is for RI compatibility, should code need to access this * field via reflection if it's not able to use public API to choose what * kind of flushing it gets. */ private int flushParm = NO_FLUSH; private boolean finished; private int compressLevel = DEFAULT_COMPRESSION; private int strategy = DEFAULT_STRATEGY; private long streamHandle = -1; private byte[] inputBuffer; private int inRead; private int inLength; private final CloseGuard guard = CloseGuard.get(); /** * Constructs a new {@code Deflater} instance using the * default <a href="#compression_level">compression level</a>. * The compression 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 the * given <a href="#compression_level">compression level</a>. * The compression 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(int level) { this(level, false); } /** * Constructs a new {@code Deflater} instance with the * given <a href="#compression_level">compression level</a>. * If {@code noHeader} is true, no ZLIB header is added to the * output. In a zip file, every entry (compressed file) comes with such a * header. The strategy can be specified using {@link #setStrategy}. */ public Deflater(int level, boolean noHeader) { if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) { throw new IllegalArgumentException("Bad level: " + level); } compressLevel = level; streamHandle = createStream(compressLevel, strategy, noHeader); guard.open("end"); } /** * Deflates the data (previously passed to {@link #setInput setInput}) into the * supplied buffer. * * @return number of bytes of compressed data written to {@code buf}. */ public int deflate(byte[] buf) { return deflate(buf, 0, buf.length); } /** * Deflates data (previously passed to {@link #setInput setInput}) into a specific * region within the supplied buffer. * * @return the number of bytes of compressed data written to {@code buf}. */ public synchronized int deflate(byte[] buf, int offset, int byteCount) { return deflateImpl(buf, offset, byteCount, flushParm); } /** * Deflates data (previously passed to {@link #setInput setInput}) into a specific * region within the supplied buffer, optionally flushing the input buffer. * * @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 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. * @throws IllegalArgumentException if {@code flush} is invalid. * @since 1.7 */ 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("Bad flush value: " + flush); } return deflateImpl(buf, offset, byteCount, flush); } private synchronized int deflateImpl(byte[] buf, int offset, int byteCount, int flush) { checkOpen(); Arrays.checkOffsetAndCount(buf.length, offset, byteCount); if (inputBuffer == null) { setInput(EmptyArray.BYTE); } return deflateImpl(buf, offset, byteCount, streamHandle, flush); } private native int deflateImpl(byte[] buf, int offset, int byteCount, long handle, int flushParm); /** * Frees all resources held onto by this deflating algorithm. 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(); endImpl(); } private void endImpl() { if (streamHandle != -1) { endImpl(streamHandle); inputBuffer = null; streamHandle = -1; } } private native void endImpl(long handle); @Override protected void finalize() { try { if (guard != null) { guard.warnIfOpen(); } synchronized (this) { end(); // to allow overriding classes to clean up endImpl(); // in case those classes don't call super.end() } } finally { try { super.finalize(); } catch (Throwable t) { throw new AssertionError(t); } } } /** * Indicates to the {@code Deflater} that all uncompressed input has been provided * to it. * * @see #finished */ public synchronized void finish() { flushParm = FINISH; } /** * Returns true if {@link #finish finish} has been called and all * data provided by {@link #setInput setInput} has been * successfully compressed and consumed by {@link #deflate deflate}. */ public synchronized boolean finished() { return finished; } /** * Returns the {@link Adler32} checksum of the uncompressed data read so far. */ public synchronized int getAdler() { checkOpen(); return getAdlerImpl(streamHandle); } private native int getAdlerImpl(long handle); /** * 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() { checkOpen(); return (int) getTotalInImpl(streamHandle); } private native long getTotalInImpl(long handle); /** * 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() { checkOpen(); return (int) getTotalOutImpl(streamHandle); } private native long getTotalOutImpl(long handle); /** * Returns true if {@link #setInput 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) { return true; } return inRead == inLength; } /** * Resets the {@code Deflater} to accept new input without affecting any * previous compression strategy or level settings. This * operation <i>must</i> be called after {@link #finished} returns * true if the {@code Deflater} is to be reused. */ public synchronized void reset() { checkOpen(); flushParm = NO_FLUSH; finished = false; resetImpl(streamHandle); inputBuffer = null; } private native void resetImpl(long handle); /** * Sets the dictionary to be used for compression by this {@code Deflater}. * 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[] dictionary) { setDictionary(dictionary, 0, dictionary.length); } /** * Sets the dictionary to be used for compression by this {@code Deflater}. * 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) { checkOpen(); Arrays.checkOffsetAndCount(buf.length, offset, byteCount); setDictionaryImpl(buf, offset, byteCount, streamHandle); } 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. */ public void setInput(byte[] buf) { setInput(buf, 0, buf.length); } /** * Sets the input buffer the {@code Deflater} will use to extract uncompressed bytes * for later compression. */ public synchronized void setInput(byte[] buf, int offset, int byteCount) { checkOpen(); 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 native void setLevelsImpl(int level, int strategy, long handle); private native void setInputImpl(byte[] buf, int offset, int byteCount, long handle); /** * Sets the given <a href="#compression_level">compression level</a> * to be used when compressing data. This value must be set * prior to calling {@link #setInput setInput}. * @throws IllegalArgumentException * If the compression level is invalid. */ public synchronized void setLevel(int level) { if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) { throw new IllegalArgumentException("Bad level: " + level); } if (inputBuffer != null) { 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 {@link #setInput setInput}. * * @throws 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("Bad strategy: " + strategy); } if (inputBuffer != null) { throw new IllegalStateException("setStrategy cannot be called after setInput"); } this.strategy = strategy; } /** * 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() { checkOpen(); return getTotalInImpl(streamHandle); } /** * 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() { checkOpen(); return getTotalOutImpl(streamHandle); } private native long createStream(int level, int strategy1, boolean noHeader1); private void checkOpen() { if (streamHandle == -1) { throw new IllegalStateException("attempt to use Deflater after calling end"); } } }