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.Inflater;
21 import java.util.zip.InflaterInputStream;
22 
23 /**
24  * Implementation of {@link Uncompressor} based on Java's built-in {@link Inflater}. Uses no-wrap by
25  * default along with a 32k read buffer and a 32k write buffer. Buffers are allocated on-demand and
26  * discarded after use. {@link Inflater} instances, which may be expensive, are also created
27  * on-demand; This can be changed by using {@link #setCaching(boolean)}.
28  */
29 public class DeflateUncompressor implements Uncompressor {
30   /**
31    * Whether to skip the standard zlib header and checksum fields when
32    * reading. Defaults to true.
33    */
34   private boolean nowrap = true;
35 
36   /**
37    * The size of the buffer used for reading data in during
38    * {@link #uncompress(InputStream, OutputStream)}.
39    */
40   private int inputBufferSize = 32768;
41 
42   /**
43    * The size of the buffer used for writing data out during
44    * {@link #uncompress(InputStream, OutputStream)}.
45    */
46   private int outputBufferSize = 32768;
47 
48   /**
49    * Cached {@link Inflater} to be used.
50    */
51   private Inflater inflater = null;
52 
53   /**
54    * Whether or not to cache {@link Inflater} instances, which is a major performance tradeoff.
55    */
56   private boolean caching = false;
57 
58   /**
59    * Returns whether to skip the standard zlib header and checksum fields when reading.
60    * @return the value
61    * @see Inflater#Inflater(boolean)
62    */
isNowrap()63   public boolean isNowrap() {
64     return nowrap;
65   }
66 
67   /**
68    * Returns the size of the buffer used for reading from the input stream in
69    * {@link #uncompress(InputStream, OutputStream)}.
70    * @return the size (default is 32768)
71    */
getInputBufferSize()72   public int getInputBufferSize() {
73     return inputBufferSize;
74   }
75 
76   /**
77    * Sets the size of the buffer used for reading from the input stream in
78    * {@link #uncompress(InputStream, OutputStream)}.
79    * NB: {@link Inflater} uses an <em>internal</em> buffer and this method adjusts the size of that
80    * buffer. This buffer is important for performance, <em>even if the {@link InputStream} is
81    * is already buffered</em>.
82    * @param inputBufferSize the size to set (default is 32768)
83    */
setInputBufferSize(int inputBufferSize)84   public void setInputBufferSize(int inputBufferSize) {
85     this.inputBufferSize = inputBufferSize;
86   }
87 
88   /**
89    * Returns the size of the buffer used for writing to the output stream in
90    * {@link #uncompress(InputStream, OutputStream)}.
91    * @return the size (default is 32768)
92    */
getOutputBufferSize()93   public int getOutputBufferSize() {
94     return outputBufferSize;
95   }
96 
97   /**
98    * Sets the size of the buffer used for writing to the output stream in
99    * {@link #uncompress(InputStream, OutputStream)}.
100    * @param outputBufferSize the size to set (default is 32768)
101    */
setOutputBufferSize(int outputBufferSize)102   public void setOutputBufferSize(int outputBufferSize) {
103     this.outputBufferSize = outputBufferSize;
104   }
105 
106   /**
107    * Sets whether or not to suppress wrapping the deflate output with the standard zlib header and
108    * checksum fields. Defaults to false.
109    * @param nowrap see {@link Inflater#Inflater(boolean)}
110    */
setNowrap(boolean nowrap)111   public void setNowrap(boolean nowrap) {
112     if (nowrap != this.nowrap) {
113       release(); // Cannot re-use the inflater any more.
114       this.nowrap = nowrap;
115     }
116   }
117 
118   /**
119    * Returns if caching is enabled.
120    * @return true if enabled, otherwise false
121    * @see #setCaching(boolean)
122    */
isCaching()123   public boolean isCaching() {
124     return caching;
125   }
126 
127   /**
128    * Sets whether or not to cache the {@link Inflater} instance. Defaults to false. If set to true,
129    * the {@link Inflater} is kept until this object is finalized or until {@link #release()} is
130    * called. Instances of {@link Inflater} can be surprisingly expensive, so caching is advised in
131    * situations where many resources need to be inflated.
132    * @param caching whether to enable caching
133    */
setCaching(boolean caching)134   public void setCaching(boolean caching) {
135     this.caching = caching;
136   }
137 
138   /**
139    * Returns the {@link Inflater} to be used, creating a new one if necessary and caching it for
140    * future use.
141    * @return the inflater
142    */
createOrResetInflater()143   protected Inflater createOrResetInflater() {
144     Inflater result = inflater;
145     if (result == null) {
146       result = new Inflater(nowrap);
147       if (caching) {
148         inflater = result;
149       }
150     } else {
151       result.reset();
152     }
153     return result;
154   }
155 
156   /**
157    * Immediately releases any cached {@link Inflater} instance.
158    */
release()159   public void release() {
160     if (inflater != null) {
161       inflater.end();
162       inflater = null;
163     }
164   }
165 
166   @Override
uncompress(InputStream compressedIn, OutputStream uncompressedOut)167   public void uncompress(InputStream compressedIn, OutputStream uncompressedOut)
168       throws IOException {
169     InflaterInputStream inflaterIn =
170         new InflaterInputStream(compressedIn, createOrResetInflater(), inputBufferSize);
171     byte[] buffer = new byte[outputBufferSize];
172     int numRead = 0;
173     while ((numRead = inflaterIn.read(buffer)) >= 0) {
174       uncompressedOut.write(buffer, 0, numRead);
175     }
176     if (!isCaching()) {
177       release();
178     }
179   }
180 }
181