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 17 @file:JvmName("-DeflaterSinkExtensions") 18 @file:Suppress("NOTHING_TO_INLINE") // Aliases to public API. 19 20 package okio 21 22 import java.util.zip.Deflater 23 24 /** 25 * A sink that uses [DEFLATE](http://tools.ietf.org/html/rfc1951) to 26 * compress data written to another source. 27 * 28 * ### Sync flush 29 * 30 * Aggressive flushing of this stream may result in reduced compression. Each 31 * call to [flush] immediately compresses all currently-buffered data; 32 * this early compression may be less effective than compression performed 33 * without flushing. 34 * 35 * This is equivalent to using [Deflater] with the sync flush option. 36 * This class does not offer any partial flush mechanism. For best performance, 37 * only call [flush] when application behavior requires it. 38 */ 39 class DeflaterSink 40 /** 41 * This internal constructor shares a buffer with its trusted caller. 42 * In general we can't share a BufferedSource because the deflater holds input 43 * bytes until they are inflated. 44 */ 45 internal constructor(private val sink: BufferedSink, private val deflater: Deflater) : Sink { 46 constructor(sink: Sink, deflater: Deflater) : this(sink.buffer(), deflater) 47 48 private var closed = false 49 50 @Throws(IOException::class) writenull51 override fun write(source: Buffer, byteCount: Long) { 52 checkOffsetAndCount(source.size, 0, byteCount) 53 54 var remaining = byteCount 55 while (remaining > 0) { 56 // Share bytes from the head segment of 'source' with the deflater. 57 val head = source.head!! 58 val toDeflate = minOf(remaining, head.limit - head.pos).toInt() 59 deflater.setInput(head.data, head.pos, toDeflate) 60 61 // Deflate those bytes into sink. 62 deflate(false) 63 64 // Mark those bytes as read. 65 source.size -= toDeflate 66 head.pos += toDeflate 67 if (head.pos == head.limit) { 68 source.head = head.pop() 69 SegmentPool.recycle(head) 70 } 71 72 remaining -= toDeflate 73 } 74 } 75 deflatenull76 private fun deflate(syncFlush: Boolean) { 77 val buffer = sink.buffer 78 while (true) { 79 val s = buffer.writableSegment(1) 80 81 // The 4-parameter overload of deflate() doesn't exist in the RI until 82 // Java 1.7, and is public (although with @hide) on Android since 2.3. 83 // The @hide tag means that this code won't compile against the Android 84 // 2.3 SDK, but it will run fine there. 85 val deflated = try { 86 if (syncFlush) { 87 deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH) 88 } else { 89 deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit) 90 } 91 } catch (npe: NullPointerException) { 92 throw IOException("Deflater already closed", npe) 93 } 94 95 if (deflated > 0) { 96 s.limit += deflated 97 buffer.size += deflated 98 sink.emitCompleteSegments() 99 } else if (deflater.needsInput()) { 100 if (s.pos == s.limit) { 101 // We allocated a tail segment, but didn't end up needing it. Recycle! 102 buffer.head = s.pop() 103 SegmentPool.recycle(s) 104 } 105 return 106 } 107 } 108 } 109 110 @Throws(IOException::class) flushnull111 override fun flush() { 112 deflate(true) 113 sink.flush() 114 } 115 finishDeflatenull116 internal fun finishDeflate() { 117 deflater.finish() 118 deflate(false) 119 } 120 121 @Throws(IOException::class) closenull122 override fun close() { 123 if (closed) return 124 125 // Emit deflated data to the underlying sink. If this fails, we still need 126 // to close the deflater and the sink; otherwise we risk leaking resources. 127 var thrown: Throwable? = null 128 try { 129 finishDeflate() 130 } catch (e: Throwable) { 131 thrown = e 132 } 133 134 try { 135 deflater.end() 136 } catch (e: Throwable) { 137 if (thrown == null) thrown = e 138 } 139 140 try { 141 sink.close() 142 } catch (e: Throwable) { 143 if (thrown == null) thrown = e 144 } 145 146 closed = true 147 148 if (thrown != null) throw thrown 149 } 150 timeoutnull151 override fun timeout(): Timeout = sink.timeout() 152 153 override fun toString() = "DeflaterSink($sink)" 154 } 155 156 /** 157 * Returns an [DeflaterSink] that DEFLATE-compresses data to this [Sink] while writing. 158 * 159 * @see DeflaterSink 160 */ 161 inline fun Sink.deflate(deflater: Deflater = Deflater()): DeflaterSink = 162 DeflaterSink(this, deflater) 163