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