Home | History | Annotate | Download | only in okio
      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