33
44package io.github.developrofthings.kapacity.io
55
6+ import io.github.developrofthings.kapacity.ExperimentalKapacityApi
67import io.github.developrofthings.kapacity.InternalKapacityApi
78import io.github.developrofthings.kapacity.Kapacity
89import io.github.developrofthings.kapacity.byte
910import java.io.File
11+ import java.io.InputStream
12+ import java.io.OutputStream
13+ import java.io.OutputStreamWriter
1014import java.nio.ByteBuffer
1115import java.nio.file.Path
1216import kotlin.io.path.fileSize
@@ -123,4 +127,119 @@ fun ByteBuffer.put(
123127 /* offset = */ sourceOffset,
124128 /* length = */ safeLength,
125129 )
130+ }
131+
132+
133+ /* *
134+ * Returns an estimate of the number of bytes that can be read (or skipped over) from this
135+ * [InputStream] without blocking, safely wrapped as a [Kapacity] instance.
136+ *
137+ * **Important Note on Java IO:** This property directly delegates to [InputStream.available].
138+ * It represents the number of bytes currently buffered locally or immediately accessible.
139+ * It does **not** represent the total remaining size of the stream or file. You should never
140+ * use this property to allocate a buffer intended to hold the entire contents of a network stream.
141+ *
142+ * @return The estimated number of non-blocking bytes currently available, represented as [Kapacity].
143+ * @throws java.io.IOException If an I/O error occurs while checking the underlying stream.
144+ */
145+ val InputStream .available: Kapacity get() = available().byte
146+
147+ /* *
148+ * Reads up to the specified [kapacity] of bytes from this input stream into the given [destination] array.
149+ *
150+ * This function safely guards against buffer overflows. It automatically calculates the available
151+ * space in the [destination] array starting from the [destinationOffset] and ensures that the number
152+ * of bytes read does not exceed this available space, even if the requested [kapacity] is larger.
153+ *
154+ * @param destination The byte array to which data is written.
155+ * @param destinationOffset The starting offset in the [destination] array where the data will be written. Defaults to 0.
156+ * @param kapacity The maximum number of bytes to read from the stream.
157+ * @return The total number of bytes read into the buffer, or `-1` if there is no more data because the end of the stream has been reached.
158+ * @throws IllegalArgumentException If [destinationOffset] is outside the bounds of the [destination] array,
159+ * or if [kapacity] represents a negative value.
160+ */
161+ fun InputStream.read (
162+ destination : ByteArray ,
163+ destinationOffset : Int = 0,
164+ kapacity : Kapacity ,
165+ ): Int {
166+ require(destinationOffset in 0 .. destination.size) {
167+ " destinationOffset ($destinationOffset ) must be between 0 and ${destination.size} "
168+ }
169+ require(kapacity.rawBytes >= 0L ) {
170+ " Cannot read a negative kapacity: $kapacity "
171+ }
172+
173+ val readLength = kapacity.rawBytesCoercedToIntRange
174+ val availableSpace = (destination.size - destinationOffset)
175+ val safeLength = minOf(
176+ a = readLength,
177+ b = availableSpace,
178+ )
179+ return this .read(
180+ /* b = */ destination,
181+ /* off = */ destinationOffset,
182+ /* len = */ safeLength
183+ )
184+ }
185+
186+ /* *
187+ * Reads up to the specified [kapacity] of bytes from this input stream into the given [destination] array,
188+ * returning the result as a [Kapacity] instance.
189+ *
190+ * Like its primitive counterpart, this function safely limits the read length to the available space
191+ * in the [destination] array to prevent buffer overflows.
192+ *
193+ * @param destination The byte array to which data is written.
194+ * @param destinationOffset The starting offset in the [destination] array where the data will be written. Defaults to 0.
195+ * @param kapacity The maximum number of bytes to read from the stream.
196+ * @return The total number of bytes read wrapped in a [Kapacity] instance, or [Kapacity.INVALID] if
197+ * the end of the stream has been reached.
198+ * @throws IllegalArgumentException If [destinationOffset] is outside the bounds of the [destination] array,
199+ * or if [kapacity] represents a negative value.
200+ */
201+ @ExperimentalKapacityApi
202+ fun InputStream.readKapacity (
203+ destination : ByteArray ,
204+ destinationOffset : Int = 0,
205+ kapacity : Kapacity ,
206+ ): Kapacity = this .read(
207+ destination = destination,
208+ destinationOffset = destinationOffset,
209+ kapacity = kapacity
210+ ).takeIf { it >= 0 }?.byte ? : Kapacity .INVALID
211+
212+ /* *
213+ * Writes up to the specified [kapacity] of bytes from the [source] array to this output stream.
214+ *
215+ * This function blocks until the bytes are written or an exception is thrown.
216+ * * **Safe Bounds:** The actual number of bytes written is safely clamped to prevent
217+ * `IndexOutOfBoundsException`. The length will be the minimum of: the requested [kapacity] or
218+ * the available data in the [source] array accounting for the [sourceOffset].
219+ *
220+ * @param source The data to write.
221+ * @param sourceOffset The start offset in the [source] array from which to begin reading. Defaults to 0.
222+ * @param kapacity The maximum number of bytes to write to the stream.
223+ * @throws java.io.IOException If an I/O error occurs.
224+ */
225+ fun OutputStream.write (
226+ source : ByteArray ,
227+ sourceOffset : Int = 0,
228+ kapacity : Kapacity ,
229+ ) {
230+ val writeLength = kapacity.rawBytesCoercedToIntRange
231+ val availableSpace = (source.size - sourceOffset)
232+ val safeLength = minOf(
233+ a = writeLength,
234+ b = availableSpace,
235+ )
236+
237+ // Exit early if capacity is 0 or offsets are out of bounds
238+ if (safeLength <= 0 ) return
239+
240+ this .write(
241+ /* b = */ source,
242+ /* off = */ sourceOffset,
243+ /* len = */ safeLength
244+ )
126245}
0 commit comments