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