1 // Copyright 2015 Google Inc. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.archivepatcher.shared;
16 
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.util.zip.Deflater;
21 import java.util.zip.DeflaterOutputStream;
22 
23 /**
24  * Implementation of {@link Compressor} based on Java's built-in {@link Deflater}. Uses default
25  * compression, the default strategy, and no-wrap by default along with a 32k read buffer and a 32k
26  * write buffer. Buffers are allocated on-demand and discarded after use.
27  */
28 public class DeflateCompressor implements Compressor {
29 
30   /**
31    * The compression level to use. Defaults to {@link Deflater#DEFAULT_COMPRESSION}.
32    */
33   private int compressionLevel = Deflater.DEFAULT_COMPRESSION;
34 
35   /**
36    * The compression strategy to use. Defaults to {@link Deflater#DEFAULT_STRATEGY}.
37    */
38   private int strategy = Deflater.DEFAULT_STRATEGY;
39 
40   /**
41    * Whether or not to suppress wrapping the deflate output with the
42    * standard zlib header and checksum fields. Defaults to true.
43    */
44   private boolean nowrap = true;
45 
46   /**
47    * The size of the buffer used for reading data in during
48    * {@link #compress(InputStream, OutputStream)}.
49    */
50   private int inputBufferSize = 32768;
51 
52   /**
53    * The size of the buffer used for writing data out during
54    * {@link #compress(InputStream, OutputStream)}.
55    */
56   private int outputBufferSize = 32768;
57 
58   /**
59    * Cached {@link Deflater} to be used.
60    */
61   private Deflater deflater = null;
62 
63   /**
64    * Whether or not to cache {@link Deflater} instances, which is a major performance tradeoff.
65    */
66   private boolean caching = false;
67 
68   /**
69    * Returns whether or not to suppress wrapping the deflate output with the standard zlib header
70    * and checksum fields.
71    * @return the value
72    * @see Deflater#Deflater(int, boolean)
73    */
isNowrap()74   public boolean isNowrap() {
75     return nowrap;
76   }
77 
78   /**
79    * Sets whether or not to suppress wrapping the deflate output with the standard zlib header and
80    * checksum fields. Defaults to false.
81    * @param nowrap see {@link Deflater#Deflater(int, boolean)}
82    */
setNowrap(boolean nowrap)83   public void setNowrap(boolean nowrap) {
84     if (nowrap != this.nowrap) {
85       release(); // Cannot re-use the deflater any more.
86       this.nowrap = nowrap;
87     }
88   }
89 
90   /**
91    * Returns the compression level that will be used, in the range 0-9.
92    * @return the level
93    */
getCompressionLevel()94   public int getCompressionLevel() {
95     return compressionLevel;
96   }
97 
98   /**
99    * Sets the compression level to be used. Defaults to {@link Deflater#BEST_COMPRESSION}.
100    * @param compressionLevel the level, in the range 0-9
101    */
setCompressionLevel(int compressionLevel)102   public void setCompressionLevel(int compressionLevel) {
103     if (compressionLevel < 0 || compressionLevel > 9) {
104       throw new IllegalArgumentException(
105           "compressionLevel must be in the range [0,9]: " + compressionLevel);
106     }
107     if (deflater != null && compressionLevel != this.compressionLevel) {
108       deflater.reset();
109       deflater.setLevel(compressionLevel);
110     }
111     this.compressionLevel = compressionLevel;
112   }
113 
114   /**
115    * Returns the strategy that will be used, from {@link Deflater}.
116    * @return the strategy
117    */
getStrategy()118   public int getStrategy() {
119     return strategy;
120   }
121 
122   /**
123    * Sets the strategy that will be used. Valid values can be found in {@link Deflater}. Defaults to
124    * {@link Deflater#DEFAULT_STRATEGY}
125    * @param strategy the strategy to be used
126    */
setStrategy(int strategy)127   public void setStrategy(int strategy) {
128     if (deflater != null && strategy != this.strategy) {
129       deflater.reset();
130       deflater.setStrategy(strategy);
131     }
132     this.strategy = strategy;
133   }
134 
135   /**
136    * Returns the size of the buffer used for reading from the input stream in
137    * {@link #compress(InputStream, OutputStream)}.
138    * @return the size (default is 32768)
139    */
getInputBufferSize()140   public int getInputBufferSize() {
141     return inputBufferSize;
142   }
143 
144   /**
145    * Sets the size of the buffer used for reading from the input stream in
146    * {@link #compress(InputStream, OutputStream)}.
147    * @param inputBufferSize the size to set (default is 32768)
148    */
setInputBufferSize(int inputBufferSize)149   public void setInputBufferSize(int inputBufferSize) {
150     this.inputBufferSize = inputBufferSize;
151   }
152 
153   /**
154    * Returns the size of the buffer used for writing to the output stream in
155    * {@link #compress(InputStream, OutputStream)}.
156    * @return the size (default is 32768)
157    */
getOutputBufferSize()158   public int getOutputBufferSize() {
159     return outputBufferSize;
160   }
161 
162   /**
163    * Sets the size of the buffer used for writing to the output stream in
164    * {@link #compress(InputStream, OutputStream)}.
165    * NB: {@link Deflater} uses an <em>internal</em> buffer and this method adjusts the size of that
166    * buffer. This buffer is important for performance, <em>even if the {@link OutputStream} is
167    * is already buffered</em>.
168    * @param outputBufferSize the size to set (default is 32768)
169    */
setOutputBufferSize(int outputBufferSize)170   public void setOutputBufferSize(int outputBufferSize) {
171     this.outputBufferSize = outputBufferSize;
172   }
173 
174   /**
175    * Returns if caching is enabled.
176    * @return true if enabled, otherwise false
177    * @see #setCaching(boolean)
178    */
isCaching()179   public boolean isCaching() {
180     return caching;
181   }
182 
183   /**
184    * Sets whether or not to cache the {@link Deflater} instance. Defaults to false. If set to true,
185    * the {@link Deflater} is kept until this object is finalized or until {@link #release()} is
186    * called. Instances of {@link Deflater} can be surprisingly expensive, so caching is advised in
187    * situations where many resources need to be deflated.
188    * @param caching whether to enable caching
189    */
setCaching(boolean caching)190   public void setCaching(boolean caching) {
191     this.caching = caching;
192   }
193 
194   /**
195    * Returns the {@link Deflater} to be used, creating a new one if necessary and caching it for
196    * future use.
197    * @return the deflater
198    */
createOrResetDeflater()199   protected Deflater createOrResetDeflater() {
200     Deflater result = deflater;
201     if (result == null) {
202       result = new Deflater(compressionLevel, nowrap);
203       result.setStrategy(strategy);
204       if (caching) {
205         deflater = result;
206       }
207     } else {
208       result.reset();
209     }
210     return result;
211   }
212 
213   /**
214    * Immediately releases any cached {@link Deflater} instance.
215    */
release()216   public void release() {
217     if (deflater != null) {
218       deflater.end();
219       deflater = null;
220     }
221   }
222 
223   @Override
compress(InputStream uncompressedIn, OutputStream compressedOut)224   public void compress(InputStream uncompressedIn, OutputStream compressedOut) throws IOException {
225     byte[] buffer = new byte[inputBufferSize];
226     DeflaterOutputStream deflaterOut =
227         new DeflaterOutputStream(compressedOut, createOrResetDeflater(), outputBufferSize);
228     int numRead = 0;
229     while ((numRead = uncompressedIn.read(buffer)) >= 0) {
230       deflaterOut.write(buffer, 0, numRead);
231     }
232     deflaterOut.finish();
233     deflaterOut.flush();
234   }
235 }
236