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(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