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.Deflater; 20 21 import static okio.Util.checkOffsetAndCount; 22 23 /** 24 * A sink that uses <a href="http://tools.ietf.org/html/rfc1951">DEFLATE</a> to 25 * compress data written to another source. 26 * 27 * <h3>Sync flush</h3> 28 * Aggressive flushing of this stream may result in reduced compression. Each 29 * call to {@link #flush} immediately compresses all currently-buffered data; 30 * this early compression may be less effective than compression performed 31 * without flushing. 32 * 33 * <p>This is equivalent to using {@link Deflater} with the sync flush option. 34 * This class does not offer any partial flush mechanism. For best performance, 35 * only call {@link #flush} when application behavior requires it. 36 */ 37 public final class DeflaterSink implements Sink { 38 private final BufferedSink sink; 39 private final Deflater deflater; 40 private boolean closed; 41 42 public DeflaterSink(Sink sink, Deflater deflater) { 43 this.sink = Okio.buffer(sink); 44 this.deflater = deflater; 45 } 46 47 @Override public void write(OkBuffer source, long byteCount) 48 throws IOException { 49 checkOffsetAndCount(source.size, 0, byteCount); 50 while (byteCount > 0) { 51 // Share bytes from the head segment of 'source' with the deflater. 52 Segment head = source.head; 53 int toDeflate = (int) Math.min(byteCount, head.limit - head.pos); 54 deflater.setInput(head.data, head.pos, toDeflate); 55 56 // Deflate those bytes into sink. 57 deflate(false); 58 59 // Mark those bytes as read. 60 source.size -= toDeflate; 61 head.pos += toDeflate; 62 if (head.pos == head.limit) { 63 source.head = head.pop(); 64 SegmentPool.INSTANCE.recycle(head); 65 } 66 67 byteCount -= toDeflate; 68 } 69 } 70 71 private void deflate(boolean syncFlush) throws IOException { 72 OkBuffer buffer = sink.buffer(); 73 while (true) { 74 Segment s = buffer.writableSegment(1); 75 76 // The 4-parameter overload of deflate() doesn't exist in the RI until 77 // Java 1.7, and is public (although with @hide) on Android since 2.3. 78 // The @hide tag means that this code won't compile against the Android 79 // 2.3 SDK, but it will run fine there. 80 int deflated = syncFlush 81 ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH) 82 : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit); 83 84 if (deflated > 0) { 85 s.limit += deflated; 86 buffer.size += deflated; 87 sink.emitCompleteSegments(); 88 } else if (deflater.needsInput()) { 89 return; 90 } 91 } 92 } 93 94 @Override public void flush() throws IOException { 95 deflate(true); 96 sink.flush(); 97 } 98 99 @Override public void close() throws IOException { 100 if (closed) return; 101 102 // Emit deflated data to the underlying sink. If this fails, we still need 103 // to close the deflater and the sink; otherwise we risk leaking resources. 104 Throwable thrown = null; 105 try { 106 deflater.finish(); 107 deflate(false); 108 } catch (Throwable e) { 109 thrown = e; 110 } 111 112 try { 113 deflater.end(); 114 } catch (Throwable e) { 115 if (thrown == null) thrown = e; 116 } 117 118 try { 119 sink.close(); 120 } catch (Throwable e) { 121 if (thrown == null) thrown = e; 122 } 123 closed = true; 124 125 if (thrown != null) Util.sneakyRethrow(thrown); 126 } 127 128 @Override public Sink deadline(Deadline deadline) { 129 sink.deadline(deadline); 130 return this; 131 } 132 133 @Override public String toString() { 134 return "DeflaterSink(" + sink + ")"; 135 } 136 } 137