xref: /aosp_15_r20/external/okio/okio/src/jvmMain/kotlin/okio/DeflaterSink.kt (revision f9742813c14b702d71392179818a9e591da8620c)
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