Home | History | Annotate | Download | only in shared
      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    */
     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    */
     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    */
     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    */
     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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    159   public void release() {
    160     if (inflater != null) {
    161       inflater.end();
    162       inflater = null;
    163     }
    164   }
    165 
    166   @Override
    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