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 */ 16package okio; 17 18import java.io.IOException; 19import java.util.zip.CRC32; 20import java.util.zip.Deflater; 21 22import 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 */ 38public 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