Home | History | Annotate | Download | only in okio
      1 /*
      2  * Copyright (C) 2014 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package okio;
     17 
     18 import java.io.IOException;
     19 import java.util.zip.CRC32;
     20 import java.util.zip.Deflater;
     21 
     22 import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
     23 
     24 /**
     25  * A sink that uses <a href="http://www.ietf.org/rfc/rfc1952.txt">GZIP</a> to
     26  * compress written data to another sink.
     27  *
     28  * <h3>Sync flush</h3>
     29  * Aggressive flushing of this stream may result in reduced compression. Each
     30  * call to {@link #flush} immediately compresses all currently-buffered data;
     31  * this early compression may be less effective than compression performed
     32  * without flushing.
     33  *
     34  * <p>This is equivalent to using {@link Deflater} with the sync flush option.
     35  * This class does not offer any partial flush mechanism. For best performance,
     36  * only call {@link #flush} when application behavior requires it.
     37  */
     38 public final class GzipSink implements Sink {
     39   /** Sink into which the GZIP format is written. */
     40   private final BufferedSink sink;
     41 
     42   /** The deflater used to compress the body. */
     43   private final Deflater deflater;
     44 
     45   /**
     46    * The deflater sink takes care of moving data between decompressed source and
     47    * compressed sink buffers.
     48    */
     49   private final DeflaterSink deflaterSink;
     50 
     51   private boolean closed;
     52 
     53   /** Checksum calculated for the compressed body. */
     54   private final CRC32 crc = new CRC32();
     55 
     56   public GzipSink(Sink sink) {
     57     if (sink == null) throw new IllegalArgumentException("sink == null");
     58     this.deflater = new Deflater(DEFAULT_COMPRESSION, true /* No wrap */);
     59     this.sink = Okio.buffer(sink);
     60     this.deflaterSink = new DeflaterSink(this.sink, deflater);
     61 
     62     writeHeader();
     63   }
     64 
     65   @Override public void write(Buffer source, long byteCount) throws IOException {
     66     if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
     67     if (byteCount == 0) return;
     68 
     69     updateCrc(source, byteCount);
     70     deflaterSink.write(source, byteCount);
     71   }
     72 
     73   @Override public void flush() throws IOException {
     74     deflaterSink.flush();
     75   }
     76 
     77   @Override public Timeout timeout() {
     78     return sink.timeout();
     79   }
     80 
     81   @Override public void close() throws IOException {
     82     if (closed) return;
     83 
     84     // This method delegates to the DeflaterSink for finishing the deflate process
     85     // but keeps responsibility for releasing the deflater's resources. This is
     86     // necessary because writeFooter needs to query the processed byte count which
     87     // only works when the deflater is still open.
     88 
     89     Throwable thrown = null;
     90     try {
     91       deflaterSink.finishDeflate();
     92       writeFooter();
     93     } catch (Throwable e) {
     94       thrown = e;
     95     }
     96 
     97     try {
     98       deflater.end();
     99     } catch (Throwable e) {
    100       if (thrown == null) thrown = e;
    101     }
    102 
    103     try {
    104       sink.close();
    105     } catch (Throwable e) {
    106       if (thrown == null) thrown = e;
    107     }
    108     closed = true;
    109 
    110     if (thrown != null) Util.sneakyRethrow(thrown);
    111   }
    112 
    113   private void writeHeader() {
    114     // Write the Gzip header directly into the buffer for the sink to avoid handling IOException.
    115     Buffer buffer = this.sink.buffer();
    116     buffer.writeShort(0x1f8b); // Two-byte Gzip ID.
    117     buffer.writeByte(0x08); // 8 == Deflate compression method.
    118     buffer.writeByte(0x00); // No flags.
    119     buffer.writeInt(0x00); // No modification time.
    120     buffer.writeByte(0x00); // No extra flags.
    121     buffer.writeByte(0x00); // No OS.
    122   }
    123 
    124   private void writeFooter() throws IOException {
    125     sink.writeIntLe((int) crc.getValue()); // CRC of original data.
    126     sink.writeIntLe(deflater.getTotalIn()); // Length of original data.
    127   }
    128 
    129   /** Updates the CRC with the given bytes. */
    130   private void updateCrc(Buffer buffer, long byteCount) {
    131     for (Segment head = buffer.head; byteCount > 0; head = head.next) {
    132       int segmentLength = (int) Math.min(byteCount, head.limit - head.pos);
    133       crc.update(head.data, head.pos, segmentLength);
    134       byteCount -= segmentLength;
    135     }
    136   }
    137 }
    138