aboutsummaryrefslogtreecommitdiff
path: root/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt
diff options
context:
space:
mode:
Diffstat (limited to 'okio/src/jvmMain/kotlin/okio/DeflaterSink.kt')
-rw-r--r--okio/src/jvmMain/kotlin/okio/DeflaterSink.kt161
1 files changed, 161 insertions, 0 deletions
diff --git a/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt b/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt
new file mode 100644
index 00000000..e71cdff8
--- /dev/null
+++ b/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("-DeflaterSinkExtensions")
+@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
+
+package okio
+
+import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
+import java.io.IOException
+import java.util.zip.Deflater
+
+/**
+ * A sink that uses [DEFLATE](http://tools.ietf.org/html/rfc1951) to
+ * compress data written to another source.
+ *
+ * ### Sync flush
+ *
+ * Aggressive flushing of this stream may result in reduced compression. Each
+ * call to [flush] immediately compresses all currently-buffered data;
+ * this early compression may be less effective than compression performed
+ * without flushing.
+ *
+ * This is equivalent to using [Deflater] with the sync flush option.
+ * This class does not offer any partial flush mechanism. For best performance,
+ * only call [flush] when application behavior requires it.
+ */
+class DeflaterSink
+/**
+ * This internal constructor shares a buffer with its trusted caller.
+ * In general we can't share a BufferedSource because the deflater holds input
+ * bytes until they are inflated.
+ */
+internal constructor(private val sink: BufferedSink, private val deflater: Deflater) : Sink {
+ constructor(sink: Sink, deflater: Deflater) : this(sink.buffer(), deflater)
+
+ private var closed = false
+
+ @Throws(IOException::class)
+ override fun write(source: Buffer, byteCount: Long) {
+ checkOffsetAndCount(source.size, 0, byteCount)
+
+ var remaining = byteCount
+ while (remaining > 0) {
+ // Share bytes from the head segment of 'source' with the deflater.
+ val head = source.head!!
+ val toDeflate = minOf(remaining, head.limit - head.pos).toInt()
+ deflater.setInput(head.data, head.pos, toDeflate)
+
+ // Deflate those bytes into sink.
+ deflate(false)
+
+ // Mark those bytes as read.
+ source.size -= toDeflate
+ head.pos += toDeflate
+ if (head.pos == head.limit) {
+ source.head = head.pop()
+ SegmentPool.recycle(head)
+ }
+
+ remaining -= toDeflate
+ }
+ }
+
+ @IgnoreJRERequirement
+ private fun deflate(syncFlush: Boolean) {
+ val buffer = sink.buffer
+ while (true) {
+ val s = buffer.writableSegment(1)
+
+ // The 4-parameter overload of deflate() doesn't exist in the RI until
+ // Java 1.7, and is public (although with @hide) on Android since 2.3.
+ // The @hide tag means that this code won't compile against the Android
+ // 2.3 SDK, but it will run fine there.
+ val deflated = if (syncFlush) {
+ deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
+ } else {
+ deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit)
+ }
+
+ if (deflated > 0) {
+ s.limit += deflated
+ buffer.size += deflated
+ sink.emitCompleteSegments()
+ } else if (deflater.needsInput()) {
+ if (s.pos == s.limit) {
+ // We allocated a tail segment, but didn't end up needing it. Recycle!
+ buffer.head = s.pop()
+ SegmentPool.recycle(s)
+ }
+ return
+ }
+ }
+ }
+
+ @Throws(IOException::class)
+ override fun flush() {
+ deflate(true)
+ sink.flush()
+ }
+
+ internal fun finishDeflate() {
+ deflater.finish()
+ deflate(false)
+ }
+
+ @Throws(IOException::class)
+ override fun close() {
+ if (closed) return
+
+ // Emit deflated data to the underlying sink. If this fails, we still need
+ // to close the deflater and the sink; otherwise we risk leaking resources.
+ var thrown: Throwable? = null
+ try {
+ finishDeflate()
+ } catch (e: Throwable) {
+ thrown = e
+ }
+
+ try {
+ deflater.end()
+ } catch (e: Throwable) {
+ if (thrown == null) thrown = e
+ }
+
+ try {
+ sink.close()
+ } catch (e: Throwable) {
+ if (thrown == null) thrown = e
+ }
+
+ closed = true
+
+ if (thrown != null) throw thrown
+ }
+
+ override fun timeout(): Timeout = sink.timeout()
+
+ override fun toString() = "DeflaterSink($sink)"
+}
+
+/**
+ * Returns an [DeflaterSink] that DEFLATE-compresses data to this [Sink] while writing.
+ *
+ * @see DeflaterSink
+ */
+inline fun Sink.deflate(deflater: Deflater = Deflater()): DeflaterSink =
+ DeflaterSink(this, deflater)