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.Deflater;
20
21import 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 */
37public 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(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   */
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
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
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
113  @Override public void flush() throws IOException {
114    deflate(true);
115    sink.flush();
116  }
117
118  void finishDeflate() throws IOException {
119    deflater.finish();
120    deflate(false);
121  }
122
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
151  @Override public Timeout timeout() {
152    return sink.timeout();
153  }
154
155  @Override public String toString() {
156    return "DeflaterSink(" + sink + ")";
157  }
158}
159