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 DeflaterSink(Sink sink, Deflater deflater)42 public DeflaterSink(Sink sink, Deflater deflater) { 43 this(Okio.buffer(sink), deflater); 44 } 45 46 /** 47 * This package-private constructor shares a buffer with its trusted caller. 48 * In general we can't share a BufferedSource because the deflater holds input 49 * bytes until they are inflated. 50 */ DeflaterSink(BufferedSink sink, Deflater deflater)51 DeflaterSink(BufferedSink sink, Deflater deflater) { 52 if (sink == null) throw new IllegalArgumentException("source == null"); 53 if (deflater == null) throw new IllegalArgumentException("inflater == null"); 54 this.sink = sink; 55 this.deflater = deflater; 56 } 57 write(Buffer source, long byteCount)58 @Override public void write(Buffer source, long byteCount) 59 throws IOException { 60 checkOffsetAndCount(source.size, 0, byteCount); 61 while (byteCount > 0) { 62 // Share bytes from the head segment of 'source' with the deflater. 63 Segment head = source.head; 64 int toDeflate = (int) Math.min(byteCount, head.limit - head.pos); 65 deflater.setInput(head.data, head.pos, toDeflate); 66 67 // Deflate those bytes into sink. 68 deflate(false); 69 70 // Mark those bytes as read. 71 source.size -= toDeflate; 72 head.pos += toDeflate; 73 if (head.pos == head.limit) { 74 source.head = head.pop(); 75 SegmentPool.recycle(head); 76 } 77 78 byteCount -= toDeflate; 79 } 80 } 81 82 // ANDROID-BEGIN 83 // @IgnoreJRERequirement 84 // ANDROID-END deflate(boolean syncFlush)85 private void deflate(boolean syncFlush) throws IOException { 86 Buffer buffer = sink.buffer(); 87 while (true) { 88 Segment s = buffer.writableSegment(1); 89 90 // The 4-parameter overload of deflate() doesn't exist in the RI until 91 // Java 1.7, and is public (although with @hide) on Android since 2.3. 92 // The @hide tag means that this code won't compile against the Android 93 // 2.3 SDK, but it will run fine there. 94 int deflated = syncFlush 95 ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH) 96 : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit); 97 98 if (deflated > 0) { 99 s.limit += deflated; 100 buffer.size += deflated; 101 sink.emitCompleteSegments(); 102 } else if (deflater.needsInput()) { 103 if (s.pos == s.limit) { 104 // We allocated a tail segment, but didn't end up needing it. Recycle! 105 buffer.head = s.pop(); 106 SegmentPool.recycle(s); 107 } 108 return; 109 } 110 } 111 } 112 flush()113 @Override public void flush() throws IOException { 114 deflate(true); 115 sink.flush(); 116 } 117 finishDeflate()118 void finishDeflate() throws IOException { 119 deflater.finish(); 120 deflate(false); 121 } 122 close()123 @Override public void close() throws IOException { 124 if (closed) return; 125 126 // Emit deflated data to the underlying sink. If this fails, we still need 127 // to close the deflater and the sink; otherwise we risk leaking resources. 128 Throwable thrown = null; 129 try { 130 finishDeflate(); 131 } catch (Throwable e) { 132 thrown = e; 133 } 134 135 try { 136 deflater.end(); 137 } catch (Throwable e) { 138 if (thrown == null) thrown = e; 139 } 140 141 try { 142 sink.close(); 143 } catch (Throwable e) { 144 if (thrown == null) thrown = e; 145 } 146 closed = true; 147 148 if (thrown != null) Util.sneakyRethrow(thrown); 149 } 150 timeout()151 @Override public Timeout timeout() { 152 return sink.timeout(); 153 } 154 toString()155 @Override public String toString() { 156 return "DeflaterSink(" + sink + ")"; 157 } 158 } 159