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