diff --git a/library/core/api/core.api b/library/core/api/core.api index 37a07fb..0ff1f8b 100644 --- a/library/core/api/core.api +++ b/library/core/api/core.api @@ -77,29 +77,45 @@ public final class io/matthewnelson/encoding/core/Decoder$OutFeed$Companion { public abstract class io/matthewnelson/encoding/core/Encoder : io/matthewnelson/encoding/core/Decoder { public static final field Companion Lio/matthewnelson/encoding/core/Encoder$Companion; public synthetic fun (Lio/matthewnelson/encoding/core/EncoderDecoder$Config;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZIIILkotlin/jvm/functions/Function3;)J + public static final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZIILkotlin/jvm/functions/Function3;)J + public static final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZII[CLkotlin/jvm/functions/Function3;)J public static final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZILkotlin/jvm/functions/Function3;)J public static final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZLkotlin/jvm/functions/Function3;)J public static final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;Z[CLkotlin/jvm/functions/Function3;)J + public static final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZIIILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZIILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZII[CLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;Z[CLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun encodeToByteArray ([BLio/matthewnelson/encoding/core/Encoder;)[B public static final fun encodeToCharArray ([BLio/matthewnelson/encoding/core/Encoder;)[C + public static final fun encodeToCharArray ([BLio/matthewnelson/encoding/core/Encoder;II)[C public static final fun encodeToString ([BLio/matthewnelson/encoding/core/Encoder;)Ljava/lang/String; + public static final fun encodeToString ([BLio/matthewnelson/encoding/core/Encoder;II)Ljava/lang/String; public final fun newEncoderFeed (Lio/matthewnelson/encoding/core/Encoder$OutFeed;)Lio/matthewnelson/encoding/core/Encoder$Feed; protected abstract fun newEncoderFeedProtected (Lio/matthewnelson/encoding/core/Encoder$OutFeed;)Lio/matthewnelson/encoding/core/Encoder$Feed; } public final class io/matthewnelson/encoding/core/Encoder$Companion { + public final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZIIILkotlin/jvm/functions/Function3;)J + public final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZIILkotlin/jvm/functions/Function3;)J + public final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZII[CLkotlin/jvm/functions/Function3;)J public final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZILkotlin/jvm/functions/Function3;)J public final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;ZLkotlin/jvm/functions/Function3;)J public final fun encodeBuffered ([BLio/matthewnelson/encoding/core/Encoder;Z[CLkotlin/jvm/functions/Function3;)J + public final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZIIILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZIILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZII[CLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;ZLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun encodeBufferedAsync ([BLio/matthewnelson/encoding/core/Encoder;Z[CLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun encodeToByteArray ([BLio/matthewnelson/encoding/core/Encoder;)[B public final fun encodeToCharArray ([BLio/matthewnelson/encoding/core/Encoder;)[C + public final fun encodeToCharArray ([BLio/matthewnelson/encoding/core/Encoder;II)[C public final fun encodeToString ([BLio/matthewnelson/encoding/core/Encoder;)Ljava/lang/String; + public final fun encodeToString ([BLio/matthewnelson/encoding/core/Encoder;II)Ljava/lang/String; } public abstract class io/matthewnelson/encoding/core/Encoder$Feed : io/matthewnelson/encoding/core/EncoderDecoder$Feed { diff --git a/library/core/api/core.klib.api b/library/core/api/core.klib.api index dd9c495..7778137 100644 --- a/library/core/api/core.klib.api +++ b/library/core/api/core.klib.api @@ -276,12 +276,20 @@ sealed class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.matth final object Companion { // io.matthewnelson.encoding.core/Encoder.Companion|null[0] final fun (kotlin/ByteArray).encodeBuffered(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/CharArray, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBuffered|encodeBuffered@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.CharArray;kotlin.Function3){}[0] final fun (kotlin/ByteArray).encodeBuffered(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBuffered|encodeBuffered@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Function3){}[0] + final fun (kotlin/ByteArray).encodeBuffered(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Int, kotlin/CharArray, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBuffered|encodeBuffered@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.CharArray;kotlin.Function3){}[0] + final fun (kotlin/ByteArray).encodeBuffered(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Int, kotlin/Int, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBuffered|encodeBuffered@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Function3){}[0] final fun (kotlin/ByteArray).encodeToByteArray(io.matthewnelson.encoding.core/Encoder<*>): kotlin/ByteArray // io.matthewnelson.encoding.core/Encoder.Companion.encodeToByteArray|encodeToByteArray@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>){}[0] final fun (kotlin/ByteArray).encodeToCharArray(io.matthewnelson.encoding.core/Encoder<*>): kotlin/CharArray // io.matthewnelson.encoding.core/Encoder.Companion.encodeToCharArray|encodeToCharArray@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>){}[0] + final fun (kotlin/ByteArray).encodeToCharArray(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Int, kotlin/Int): kotlin/CharArray // io.matthewnelson.encoding.core/Encoder.Companion.encodeToCharArray|encodeToCharArray@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Int;kotlin.Int){}[0] final fun (kotlin/ByteArray).encodeToString(io.matthewnelson.encoding.core/Encoder<*>): kotlin/String // io.matthewnelson.encoding.core/Encoder.Companion.encodeToString|encodeToString@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>){}[0] + final fun (kotlin/ByteArray).encodeToString(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Int, kotlin/Int): kotlin/String // io.matthewnelson.encoding.core/Encoder.Companion.encodeToString|encodeToString@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Int;kotlin.Int){}[0] + final inline fun (kotlin/ByteArray).encodeBuffered(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Int, noinline kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBuffered|encodeBuffered@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.Function3){}[0] final inline fun (kotlin/ByteArray).encodeBuffered(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, noinline kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBuffered|encodeBuffered@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Function3){}[0] final suspend fun (kotlin/ByteArray).encodeBufferedAsync(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/CharArray, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBufferedAsync|encodeBufferedAsync@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.CharArray;kotlin.coroutines.SuspendFunction3){}[0] final suspend fun (kotlin/ByteArray).encodeBufferedAsync(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBufferedAsync|encodeBufferedAsync@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.coroutines.SuspendFunction3){}[0] + final suspend fun (kotlin/ByteArray).encodeBufferedAsync(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Int, kotlin/CharArray, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBufferedAsync|encodeBufferedAsync@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.CharArray;kotlin.coroutines.SuspendFunction3){}[0] + final suspend fun (kotlin/ByteArray).encodeBufferedAsync(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Int, kotlin/Int, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBufferedAsync|encodeBufferedAsync@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.coroutines.SuspendFunction3){}[0] + final suspend inline fun (kotlin/ByteArray).encodeBufferedAsync(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Int, noinline kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBufferedAsync|encodeBufferedAsync@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.coroutines.SuspendFunction3){}[0] final suspend inline fun (kotlin/ByteArray).encodeBufferedAsync(io.matthewnelson.encoding.core/Encoder<*>, kotlin/Boolean, noinline kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Encoder.Companion.encodeBufferedAsync|encodeBufferedAsync@kotlin.ByteArray(io.matthewnelson.encoding.core.Encoder<*>;kotlin.Boolean;kotlin.coroutines.SuspendFunction3){}[0] } } diff --git a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Decoder.kt b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Decoder.kt index 265f4b9..237da68 100644 --- a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Decoder.kt +++ b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Decoder.kt @@ -297,11 +297,12 @@ public sealed class Decoder(public val config: C) { } /** - * Decode a [CharSequence] using a maximum array size of [DEFAULT_BUFFER_SIZE]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharSequence] using a buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [DEFAULT_BUFFER_SIZE], then an array of that size will be allocated + * equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -363,11 +364,12 @@ public sealed class Decoder(public val config: C) { ): Long = decodeBuffered(decoder, throwOnOverflow, DEFAULT_BUFFER_SIZE, action) /** - * Decode a [CharSequence] using a maximum array size of [maxBufSize]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharSequence] using a buffer of maximum size [maxBufSize]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [maxBufSize], then an array of that size will be allocated + * equal to the [maxBufSize], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -405,7 +407,7 @@ public sealed class Decoder(public val config: C) { * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception * will be ignored and stream decoding to the buffer will continue. - * @param [maxBufSize] The maximum size array this function will allocate. Must + * @param [maxBufSize] The maximum size buffer this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The function to flush the buffer to; a destination to "write" * decoded data to whereby `len` is the number of bytes within `buf`, starting @@ -442,7 +444,8 @@ public sealed class Decoder(public val config: C) { /** * Decode a [CharSequence] using the provided pre-allocated, reusable, [buf] array. - * The decoding operation will stream decoded bytes to the provided array, flushing + * + * The decoding operation will stream decoded bytes to the provided [buf], flushing * to [action] when needed. If the pre-calculated size returned by * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or equal to the [buf] * size, then [action] is only invoked once (single-shot decoding). In the event that @@ -527,11 +530,12 @@ public sealed class Decoder(public val config: C) { ) /** - * Decode a [CharSequence] using a maximum array size of [DEFAULT_BUFFER_SIZE]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharSequence] using a buffer of maximum of [DEFAULT_BUFFER_SIZE]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [DEFAULT_BUFFER_SIZE], then an array of that size will be allocated + * equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -595,11 +599,12 @@ public sealed class Decoder(public val config: C) { ): Long = decodeBufferedAsync(decoder, throwOnOverflow, DEFAULT_BUFFER_SIZE, action) /** - * Decode a [CharSequence] using a maximum array size of [maxBufSize]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharSequence] using a buffer of maximum size [maxBufSize]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [maxBufSize], then an array of that size will be allocated + * equal to the [maxBufSize], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -639,7 +644,7 @@ public sealed class Decoder(public val config: C) { * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception * will be ignored and stream decoding to the buffer will continue. - * @param [maxBufSize] The maximum size array this function will allocate. Must + * @param [maxBufSize] The maximum size buffer this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The suspend function to flush the buffer to; a destination to * "write" decoded data to whereby `len` is the number of bytes within `buf`, @@ -677,7 +682,8 @@ public sealed class Decoder(public val config: C) { /** * Decode a [CharSequence] using the provided pre-allocated, reusable, [buf] array. - * The decoding operation will stream decoded bytes to the provided array, flushing + * + * The decoding operation will stream decoded bytes to the provided [buf], flushing * to [action] when needed. If the pre-calculated size returned by * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or equal to the [buf] * size, then [action] is only invoked once (single-shot decoding). In the event that @@ -766,11 +772,12 @@ public sealed class Decoder(public val config: C) { ) /** - * Decode a [CharArray] using a maximum array size of [DEFAULT_BUFFER_SIZE]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharArray] using a buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [DEFAULT_BUFFER_SIZE], then an array of that size will be allocated + * equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -834,11 +841,12 @@ public sealed class Decoder(public val config: C) { ): Long = decodeBuffered(decoder, throwOnOverflow, DEFAULT_BUFFER_SIZE, action) /** - * Decode a [CharArray] using a maximum array size of [maxBufSize]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharArray] using a buffer of maximum size [maxBufSize]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [maxBufSize], then an array of that size will be allocated + * equal to the [maxBufSize], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -878,7 +886,7 @@ public sealed class Decoder(public val config: C) { * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception * will be ignored and stream decoding to the buffer will continue. - * @param [maxBufSize] The maximum size array this function will allocate. Must + * @param [maxBufSize] The maximum size buffer this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The function to flush the buffer to; a destination to "write" * decoded data to whereby `len` is the number of bytes within `buf`, starting @@ -915,7 +923,8 @@ public sealed class Decoder(public val config: C) { /** * Decode a [CharArray] using the provided pre-allocated, reusable, [buf] array. - * The decoding operation will stream decoded bytes to the provided array, flushing + * + * The decoding operation will stream decoded bytes to the provided [buf], flushing * to [action] when needed. If the pre-calculated size returned by * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or equal to the [buf] * size, then [action] is only invoked once (single-shot decoding). In the event that @@ -1004,11 +1013,12 @@ public sealed class Decoder(public val config: C) { ) /** - * Decode a [CharArray] using a maximum array size of [DEFAULT_BUFFER_SIZE]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharArray] using a buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [DEFAULT_BUFFER_SIZE], then an array of that size will be allocated + * equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -1073,11 +1083,12 @@ public sealed class Decoder(public val config: C) { ): Long = decodeBufferedAsync(decoder, throwOnOverflow, DEFAULT_BUFFER_SIZE, action) /** - * Decode a [CharArray] using a maximum array size of [maxBufSize]. - * The decoding operation will allocate a single array, streaming decoded bytes + * Decode a [CharArray] using a buffer of maximum size [maxBufSize]. + * + * The decoding operation will allocate a single buffer, streaming decoded bytes * to it and flushing to [action] when needed. If the pre-calculated size * returned by [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or - * equal to the [maxBufSize], then an array of that size will be allocated + * equal to the [maxBufSize], then a buffer of that size will be allocated * and [action] is only invoked once (single-shot decoding). In the event that * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] throws its [EncodingSizeException] * due to an overflow (i.e. decoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] @@ -1118,7 +1129,7 @@ public sealed class Decoder(public val config: C) { * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception * will be ignored and stream decoding to the buffer will continue. - * @param [maxBufSize] The maximum size array this function will allocate. Must + * @param [maxBufSize] The maximum size buffer this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The suspend function to flush the buffer to; a destination to * "write" decoded data to whereby `len` is the number of bytes within `buf`, @@ -1156,7 +1167,8 @@ public sealed class Decoder(public val config: C) { /** * Decode a [CharArray] using the provided pre-allocated, reusable, [buf] array. - * The decoding operation will stream decoded bytes to the provided array, flushing + * + * The decoding operation will stream decoded bytes to the provided [buf], flushing * to [action] when needed. If the pre-calculated size returned by * [EncoderDecoder.Config.decodeOutMaxSizeOrFail] is less than or equal to the [buf] * size, then [action] is only invoked once (single-shot decoding). In the event that diff --git a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Encoder.kt b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Encoder.kt index 3753322..220a070 100644 --- a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Encoder.kt +++ b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Encoder.kt @@ -17,10 +17,12 @@ package io.matthewnelson.encoding.core +import io.matthewnelson.encoding.core.Encoder.OutFeed import io.matthewnelson.encoding.core.EncoderDecoder.Companion.DEFAULT_BUFFER_SIZE +import io.matthewnelson.encoding.core.internal.checkBounds import io.matthewnelson.encoding.core.internal.closedException -import io.matthewnelson.encoding.core.internal.encode -import io.matthewnelson.encoding.core.internal.encodeBuffered +import io.matthewnelson.encoding.core.internal.encodeUnsafe +import io.matthewnelson.encoding.core.internal.encodeBufferedUnsafe import io.matthewnelson.encoding.core.util.LineBreakOutFeed import io.matthewnelson.encoding.core.util.wipe import kotlin.coroutines.cancellation.CancellationException @@ -226,14 +228,32 @@ public sealed class Encoder(config: C): Decoder(con * */ @JvmStatic public fun ByteArray.encodeToString(encoder: Encoder<*>): String { - val maxSize = encoder.config.encodeOutMaxSize(size) - val sb = StringBuilder(maxSize) - encoder.encode(this, _outFeed = { OutFeed(sb::append) }) - val result = sb.toString() - if (encoder.config.backFillBuffers) { - sb.wipe() - } - return result + return encodeToStringUnsafe(encoder, 0, size) + } + + /** + * Encode [len] number of bytes from the array, starting at index [offset]. + * + * @param [encoder] The [Encoder] to use. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * + * @return The [String] of encoded data. + * + * @see [encodeToCharArray] + * @see [encodeBuffered] + * @see [encodeBufferedAsync] + * + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * throws an exception (i.e. output would exceed [Int.MAX_VALUE]). + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public fun ByteArray.encodeToString(encoder: Encoder<*>, offset: Int, len: Int): String { + checkBounds(offset, len) + return encodeToStringUnsafe(encoder, offset, len) } /** @@ -254,24 +274,41 @@ public sealed class Encoder(config: C): Decoder(con * */ @JvmStatic public fun ByteArray.encodeToCharArray(encoder: Encoder<*>): CharArray { - val maxSize = encoder.config.encodeOutMaxSize(size) - var i = 0 - val a = CharArray(maxSize) - encoder.encode(this, _outFeed = { OutFeed { c -> a[i++] = c } }) - if (i == maxSize) return a - val copy = a.copyOf(i) - if (encoder.config.backFillBuffers) { - a.fill('\u0000', 0, i) - } - return copy + return encodeToCharArrayUnsafe(encoder, 0, size) } /** - * Encode a [ByteArray] using a maximum array size of [DEFAULT_BUFFER_SIZE]. - * The encoding operation will allocate a single array, streaming encoded + * Encode [len] number of bytes from the array, starting at index [offset]. + * + * @param [encoder] The [Encoder] to use. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * + * @return The [CharArray] of encoded data. + * + * @see [encodeToCharArray] + * @see [encodeBuffered] + * @see [encodeBufferedAsync] + * + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * throws an exception (i.e. output would exceed [Int.MAX_VALUE]). + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public fun ByteArray.encodeToCharArray(encoder: Encoder<*>, offset: Int, len: Int): CharArray { + checkBounds(offset, len) + return encodeToCharArrayUnsafe(encoder, offset, len) + } + + /** + * Encode a [ByteArray] using a buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The encoding operation will allocate a single buffer, streaming encoded * characters to it and flushing to [action] when needed. If the * pre-calculated size returned by [EncoderDecoder.Config.encodeOutMaxSize] - * is less than or equal to the [DEFAULT_BUFFER_SIZE], then an array of that + * is less than or equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that * size will be allocated and [action] is only invoked once (single-shot * encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) @@ -319,11 +356,70 @@ public sealed class Encoder(config: C): Decoder(con ): Long = encodeBuffered(encoder, throwOnOverflow, DEFAULT_BUFFER_SIZE, action) /** - * Encode a [ByteArray] using a maximum array size of [maxBufSize]. - * The encoding operation will allocate a single array, streaming encoded + * Encode [len] number of bytes from the array, starting at index [offset], using + * a buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The encoding operation will allocate a single buffer, streaming encoded + * characters to it and flushing to [action] when needed. If the pre-calculated + * size returned by [EncoderDecoder.Config.encodeOutMaxSize] is less than or + * equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that size will be allocated + * and [action] is only invoked once (single-shot encoding). In the event that + * [EncoderDecoder.Config.encodeOutMaxSize] throws its [EncodingSizeException] + * (i.e. encoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] is `false`, + * or its return value is greater than [DEFAULT_BUFFER_SIZE], then this function + * will always stream encode to a buffer while flushing to [action] until the + * encoding operation has completed. + * + * **NOTE:** Documented exceptions thrown by this function do not include those + * for which [action] may throw. + * + * **NOTE:** The [Encoder] implementation must be compatible with version `2.6.0+` + * APIs and define a [EncoderDecoder.Config.maxEncodeEmit]. If the value is `-1` (i.e. + * it has not updated to the new API yet), then this function will fail with an + * [EncodingException]. All implementations provided by this library have been updated + * to meet the API requirement; only [EncoderDecoder] implementations external to this + * library that have not updated yet may fail when using them with [encodeBuffered] + * and [encodeBufferedAsync] APIs. + * + * @param [encoder] The [Encoder] to use. + * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] + * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception + * is ignored and stream encoding to the buffer will continue. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * @param [action] The function to flush the buffer to; a destination to "write" + * encoded data to whereby `len` is the number of characters within `buf`, starting + * at index `offset`, to "write". + * + * @return The number of encoded characters. + * + * @see [encodeToString] + * @see [encodeToCharArray] + * @see [encodeBufferedAsync] + * + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and + * [throwOnOverflow] is `true`. + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public inline fun ByteArray.encodeBuffered( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + offset: Int, + len: Int, + noinline action: (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long = encodeBuffered(encoder, throwOnOverflow, offset, len, DEFAULT_BUFFER_SIZE, action) + + /** + * Encode a [ByteArray] using a buffer of maximum size [maxBufSize]. + * + * The encoding operation will allocate a single buffer, streaming encoded * characters to it and flushing to [action] when needed. If the * pre-calculated size returned by [EncoderDecoder.Config.encodeOutMaxSize] - * is less than or equal to the [maxBufSize], then an array of that + * is less than or equal to the [maxBufSize], then a buffer of that * size will be allocated and [action] is only invoked once (single-shot * encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) @@ -347,7 +443,7 @@ public sealed class Encoder(config: C): Decoder(con * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception * is ignored and stream encoding to the buffer will continue. - * @param [maxBufSize] The maximum size array this function will allocate. Must + * @param [maxBufSize] The maximum size buffer this function will allocate. Must * be greater than [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. * @param [action] The function to flush the buffer to; a destination to "write" * encoded data to whereby `len` is the number of characters within `buf`, starting @@ -373,17 +469,93 @@ public sealed class Encoder(config: C): Decoder(con throwOnOverflow: Boolean, maxBufSize: Int, action: (buf: CharArray, offset: Int, len: Int) -> Unit, - ): Long = encoder.encodeBuffered( + ): Long = encoder.encodeBufferedUnsafe( data = this, + offset = 0, + len = size, buf = null, maxBufSize = maxBufSize, throwOnOverflow = throwOnOverflow, _action = action, ) + /** + * Encode [len] number of bytes from the array, starting at index [offset], using + * a buffer of maximum size [maxBufSize]. + * + * The encoding operation will allocate a single buffer, streaming encoded characters + * to it and flushing to [action] when needed. If the pre-calculated size returned + * by [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [maxBufSize], + * then a buffer of that size will be allocated and [action] is only invoked once + * (single-shot encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] + * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) + * while [throwOnOverflow] is `false`, or its return value is greater than [maxBufSize], + * then this function will always stream encode to a buffer while flushing to [action] + * until the encoding operation has completed. + * + * **NOTE:** Documented exceptions thrown by this function do not include those + * for which [action] may throw. + * + * **NOTE:** The [Encoder] implementation must be compatible with version `2.6.0+` + * APIs and define a [EncoderDecoder.Config.maxEncodeEmit]. If the value is `-1` (i.e. + * it has not updated to the new API yet), then this function will fail with an + * [EncodingException]. All implementations provided by this library have been updated + * to meet the API requirement; only [EncoderDecoder] implementations external to this + * library that have not updated yet may fail when using them with [encodeBuffered] + * and [encodeBufferedAsync] APIs. + * + * @param [encoder] The [Encoder] to use. + * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] + * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception + * is ignored and stream encoding to the buffer will continue. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * @param [maxBufSize] The maximum size buffer this function will allocate. Must + * be greater than [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @param [action] The function to flush the buffer to; a destination to "write" + * encoded data to whereby `len` is the number of characters within `buf`, starting + * at index `offset`, to "write". + * + * @return The number of encoded characters. + * + * @see [encodeToString] + * @see [encodeToCharArray] + * @see [encodeBufferedAsync] + * + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and + * [throwOnOverflow] is `true`. + * @throws [IllegalArgumentException] If [maxBufSize] is less than or equal to + * [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public fun ByteArray.encodeBuffered( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + offset: Int, + len: Int, + maxBufSize: Int, + action: (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long { + checkBounds(offset, len) + return encoder.encodeBufferedUnsafe( + data = this, + offset = offset, + len = len, + buf = null, + maxBufSize = maxBufSize, + throwOnOverflow = throwOnOverflow, + _action = action, + ) + } + /** * Encode a [ByteArray] using the provided pre-allocated, reusable, [buf] array. - * The encoding operation will stream encoded characters to the provided array, + * + * The encoding operation will stream encoded characters to the provided [buf], * flushing to [action] when needed. If the pre-calculated size returned by * [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [buf] * size, then [action] is only invoked once (single-shot encoding). In the event @@ -437,8 +609,10 @@ public sealed class Encoder(config: C): Decoder(con throwOnOverflow: Boolean, buf: CharArray, action: (buf: CharArray, offset: Int, len: Int) -> Unit, - ): Long = encoder.encodeBuffered( + ): Long = encoder.encodeBufferedUnsafe( data = this, + offset = 0, + len = size, buf = buf, maxBufSize = buf.size, throwOnOverflow = throwOnOverflow, @@ -446,11 +620,88 @@ public sealed class Encoder(config: C): Decoder(con ) /** - * Encode a [ByteArray] using a maximum array size of [DEFAULT_BUFFER_SIZE]. - * The encoding operation will allocate a single array, streaming encoded + * Encode [len] number of bytes from the array, starting at index [offset], using + * the provided pre-allocated, reusable, [buf] array. + * + * The encoding operation will stream encoded characters to the provided [buf], + * flushing to [action] when needed. If the pre-calculated size returned by + * [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [buf] + * size, then [action] is only invoked once (single-shot encoding). In the event + * that [EncoderDecoder.Config.encodeOutMaxSize] throws its [EncodingSizeException] + * (i.e. encoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] is `false`, + * or its return value is greater than [buf] size, then this function will always + * stream encode to a buffer while flushing to [action] until the encoding operation + * has completed. + * + * **NOTE:** Documented exceptions thrown by this function do not include those + * for which [action] may throw. + * + * **NOTE:** If [EncoderDecoder.Config.backFillBuffers] is `true`, provided [buf] + * array will be back-filled with null character `\u0000` upon encoding completion. + * + * **NOTE:** The [Encoder] implementation must be compatible with version `2.6.0+` + * APIs and define a [EncoderDecoder.Config.maxEncodeEmit]. If the value is `-1` (i.e. + * it has not updated to the new API yet), then this function will fail with an + * [EncodingException]. All implementations provided by this library have been updated + * to meet the API requirement; only [EncoderDecoder] implementations external to this + * library that have not updated yet may fail when using them with [encodeBuffered] + * and [encodeBufferedAsync] APIs. + * + * @param [encoder] The [Encoder] to use. + * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] + * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception + * is ignored and stream encoding to the buffer will continue. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * @param [buf] The pre-allocated array to use as the buffer. Its size must + * be greater than [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @param [action] The function to flush the buffer to; a destination to "write" + * encoded data to whereby `len` is the number of characters within `buf`, starting + * at index `offset`, to "write". + * + * @return The number of encoded characters. + * + * @see [encodeToString] + * @see [encodeToCharArray] + * @see [encodeBufferedAsync] + * + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and + * [throwOnOverflow] is `true`. + * @throws [IllegalArgumentException] If [buf] size is less than or equal to + * [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public fun ByteArray.encodeBuffered( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + offset: Int, + len: Int, + buf: CharArray, + action: (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long { + checkBounds(offset, len) + return encoder.encodeBufferedUnsafe( + data = this, + offset = offset, + len = len, + buf = buf, + maxBufSize = buf.size, + throwOnOverflow = throwOnOverflow, + _action = action, + ) + } + + /** + * Encode a [ByteArray] using a buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The encoding operation will allocate a single buffer, streaming encoded * characters to it and flushing to [action] when needed. If the * pre-calculated size returned by [EncoderDecoder.Config.encodeOutMaxSize] - * is less than or equal to the [DEFAULT_BUFFER_SIZE], then an array of that + * is less than or equal to the [DEFAULT_BUFFER_SIZE], then a buffer of that * size will be allocated and [action] is only invoked once (single-shot * encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) @@ -490,7 +741,6 @@ public sealed class Encoder(config: C): Decoder(con * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and * [throwOnOverflow] is `true`. - * and [throwOnOverflow] is `true`. * */ @JvmStatic public suspend inline fun ByteArray.encodeBufferedAsync( @@ -500,11 +750,70 @@ public sealed class Encoder(config: C): Decoder(con ): Long = encodeBufferedAsync(encoder, throwOnOverflow, DEFAULT_BUFFER_SIZE, action) /** - * Encode a [ByteArray] using a maximum array size of [maxBufSize]. - * The encoding operation will allocate a single array, streaming encoded + * Encode [len] number of bytes from the array, starting at index [offset], using a + * buffer of maximum size [DEFAULT_BUFFER_SIZE]. + * + * The encoding operation will allocate a single buffer, streaming encoded characters + * to it and flushing to [action] when needed. If the pre-calculated size returned by + * [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [DEFAULT_BUFFER_SIZE], + * then a buffer of that size will be allocated and [action] is only invoked once + * (single-shot encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] + * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) while + * [throwOnOverflow] is `false`, or its return value is greater than [DEFAULT_BUFFER_SIZE], + * then this function will always stream encode to a buffer while flushing to [action] + * until the encoding operation has completed. + * + * **NOTE:** Documented exceptions thrown by this function do not include those + * for which [action] may throw. + * + * **NOTE:** The [Encoder] implementation must be compatible with version `2.6.0+` + * APIs and define a [EncoderDecoder.Config.maxEncodeEmit]. If the value is `-1` (i.e. + * it has not updated to the new API yet), then this function will fail with an + * [EncodingException]. All implementations provided by this library have been updated + * to meet the API requirement; only [EncoderDecoder] implementations external to this + * library that have not updated yet may fail when using them with [encodeBuffered] + * and [encodeBufferedAsync] APIs. + * + * @param [encoder] The [Encoder] to use. + * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] + * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception + * is ignored and stream encoding to the buffer will continue. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * @param [action] The suspend function to flush the buffer to; a destination to + * "write" encoded data to whereby `len` is the number of characters within `buf`, + * starting at index `offset`, to "write". + * + * @return The number of encoded characters. + * + * @see [encodeToString] + * @see [encodeToCharArray] + * @see [encodeBuffered] + * + * @throws [CancellationException] + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and + * [throwOnOverflow] is `true`. + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public suspend inline fun ByteArray.encodeBufferedAsync( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + offset: Int, + len: Int, + noinline action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long = encodeBufferedAsync(encoder, throwOnOverflow, offset, len, DEFAULT_BUFFER_SIZE, action) + + /** + * Encode a [ByteArray] using a buffer of maximum size [maxBufSize]. + * + * The encoding operation will allocate a single buffer, streaming encoded * characters to it and flushing to [action] when needed. If the * pre-calculated size returned by [EncoderDecoder.Config.encodeOutMaxSize] - * is less than or equal to the [maxBufSize], then an array of that + * is less than or equal to the [maxBufSize], then a buffer of that * size will be allocated and [action] is only invoked once (single-shot * encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) @@ -528,7 +837,7 @@ public sealed class Encoder(config: C): Decoder(con * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception * is ignored and stream encoding to the buffer will continue. - * @param [maxBufSize] The maximum size array this function will allocate. Must + * @param [maxBufSize] The maximum size buffer this function will allocate. Must * be greater than [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. * @param [action] The suspend function to flush the buffer to; a destination to * "write" encoded data to whereby `len` is the number of characters within `buf`, @@ -555,17 +864,94 @@ public sealed class Encoder(config: C): Decoder(con throwOnOverflow: Boolean, maxBufSize: Int, action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, - ): Long = encoder.encodeBuffered( + ): Long = encoder.encodeBufferedUnsafe( data = this, + offset = 0, + len = size, buf = null, maxBufSize = maxBufSize, throwOnOverflow = throwOnOverflow, - _action = { buf, offset, len -> action(buf, offset, len) }, + _action = { _buf, _offset, _len -> action(_buf, _offset, _len) }, ) + /** + * Encode [len] number of bytes from the array, starting at index [offset], using + * a buffer of maximum size [maxBufSize]. + * + * The encoding operation will allocate a single buffer, streaming encoded characters + * to it and flushing to [action] when needed. If the pre-calculated size returned by + * [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [maxBufSize], + * then a buffer of that size will be allocated and [action] is only invoked once + * (single-shot encoding). In the event that [EncoderDecoder.Config.encodeOutMaxSize] + * throws its [EncodingSizeException] (i.e. encoding would exceed [Int.MAX_VALUE]) + * while [throwOnOverflow] is `false`, or its return value is greater than [maxBufSize], + * then this function will always stream encode to a buffer while flushing to [action] + * until the encoding operation has completed. + * + * **NOTE:** Documented exceptions thrown by this function do not include those + * for which [action] may throw. + * + * **NOTE:** The [Encoder] implementation must be compatible with version `2.6.0+` + * APIs and define a [EncoderDecoder.Config.maxEncodeEmit]. If the value is `-1` (i.e. + * it has not updated to the new API yet), then this function will fail with an + * [EncodingException]. All implementations provided by this library have been updated + * to meet the API requirement; only [EncoderDecoder] implementations external to this + * library that have not updated yet may fail when using them with [encodeBuffered] + * and [encodeBufferedAsync] APIs. + * + * @param [encoder] The [Encoder] to use. + * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] + * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception + * is ignored and stream encoding to the buffer will continue. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * @param [maxBufSize] The maximum size buffer this function will allocate. Must + * be greater than [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @param [action] The suspend function to flush the buffer to; a destination to + * "write" encoded data to whereby `len` is the number of characters within `buf`, + * starting at index `offset`, to "write". + * + * @return The number of encoded characters. + * + * @see [encodeToString] + * @see [encodeToCharArray] + * @see [encodeBuffered] + * + * @throws [CancellationException] + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and + * [throwOnOverflow] is `true`. + * @throws [IllegalArgumentException] If [maxBufSize] is less than or equal to + * [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public suspend fun ByteArray.encodeBufferedAsync( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + offset: Int, + len: Int, + maxBufSize: Int, + action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long { + checkBounds(offset, len) + return encoder.encodeBufferedUnsafe( + data = this, + offset = offset, + len = len, + buf = null, + maxBufSize = maxBufSize, + throwOnOverflow = throwOnOverflow, + _action = { _buf, _offset, _len -> action(_buf, _offset, _len) }, + ) + } + /** * Encode a [ByteArray] using the provided pre-allocated, reusable, [buf] array. - * The encoding operation will stream encoded characters to the provided array, + * + * The encoding operation will stream encoded characters to the provided [buf], * flushing to [action] when needed. If the pre-calculated size returned by * [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [buf] * size, then [action] is only invoked once (single-shot encoding). In the event @@ -620,14 +1006,93 @@ public sealed class Encoder(config: C): Decoder(con throwOnOverflow: Boolean, buf: CharArray, action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, - ): Long = encoder.encodeBuffered( + ): Long = encoder.encodeBufferedUnsafe( data = this, + offset = 0, + len = size, buf = buf, maxBufSize = buf.size, throwOnOverflow = throwOnOverflow, - _action = { _buf, offset, len -> action(_buf, offset, len) }, + _action = { _buf, _offset, _len -> action(_buf, _offset, _len) }, ) + /** + * Encode [len] number of bytes from the array, starting at index [offset], using + * the provided pre-allocated, reusable, [buf] array. + * + * The encoding operation will stream encoded characters to the provided [buf], + * flushing to [action] when needed. If the pre-calculated size returned by + * [EncoderDecoder.Config.encodeOutMaxSize] is less than or equal to the [buf] + * size, then [action] is only invoked once (single-shot encoding). In the event + * that [EncoderDecoder.Config.encodeOutMaxSize] throws its [EncodingSizeException] + * (i.e. encoding would exceed [Int.MAX_VALUE]) while [throwOnOverflow] is `false`, + * or its return value is greater than [buf] size, then this function will always + * stream encode to a buffer while flushing to [action] until the encoding + * operation has completed. + * + * **NOTE:** Documented exceptions thrown by this function do not include those + * for which [action] may throw. + * + * **NOTE:** If [EncoderDecoder.Config.backFillBuffers] is `true`, provided [buf] + * array will be back-filled with null character `\u0000` upon encoding completion. + * + * **NOTE:** The [Encoder] implementation must be compatible with version `2.6.0+` + * APIs and define a [EncoderDecoder.Config.maxEncodeEmit]. If the value is `-1` (i.e. + * it has not updated to the new API yet), then this function will fail with an + * [EncodingException]. All implementations provided by this library have been updated + * to meet the API requirement; only [EncoderDecoder] implementations external to this + * library that have not updated yet may fail when using them with [encodeBuffered] + * and [encodeBufferedAsync] APIs. + * + * @param [encoder] The [Encoder] to use. + * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.encodeOutMaxSize] + * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception + * is ignored and stream encoding to the buffer will continue. + * @param [offset] The index in the array to start at. + * @param [len] The number of bytes, starting at index [offset]. + * @param [buf] The pre-allocated array to use as the buffer. Its size must + * be greater than [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @param [action] The suspend function to flush the buffer to; a destination to + * "write" encoded data to whereby `len` is the number of characters within `buf`, + * starting at index `offset`, to "write". + * + * @return The number of encoded characters. + * + * @see [encodeToString] + * @see [encodeToCharArray] + * @see [encodeBuffered] + * + * @throws [CancellationException] + * @throws [EncodingException] If the [encoder] is configured to reject something, + * such as `UTF-8` byte to text transformations rejecting invalid byte sequences. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * threw its exception (i.e. output would exceed [Int.MAX_VALUE]) and + * [throwOnOverflow] is `true`. + * @throws [IllegalArgumentException] If [buf] size is less than or equal to + * [EncoderDecoder.Config.maxEncodeEmitWithLineBreak]. + * @throws [IndexOutOfBoundsException] If [offset] or [len] are inappropriate. + * */ + @JvmStatic + public suspend fun ByteArray.encodeBufferedAsync( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + offset: Int, + len: Int, + buf: CharArray, + action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long { + checkBounds(offset, len) + return encoder.encodeBufferedUnsafe( + data = this, + offset = offset, + len = len, + buf = buf, + maxBufSize = buf.size, + throwOnOverflow = throwOnOverflow, + _action = { _buf, _offset, _len -> action(_buf, _offset, _len) }, + ) + } + /** * DEPRECATED since `2.3.0` * @throws [EncodingException] If the [encoder] is configured to reject something, @@ -644,7 +1109,7 @@ public sealed class Encoder(config: C): Decoder(con val maxSize = encoder.config.encodeOutMaxSize(size) var i = 0 val a = ByteArray(maxSize) - encoder.encode(this, _outFeed = { OutFeed { char -> a[i++] = char.code.toByte() } }) + encoder.encodeUnsafe(this, 0, size, _outFeed = { OutFeed { char -> a[i++] = char.code.toByte() } }) if (i == maxSize) return a val copy = a.copyOf(i) if (encoder.config.backFillBuffers) { @@ -654,3 +1119,29 @@ public sealed class Encoder(config: C): Decoder(con } } } + +// Does not check offset/len, thus the *Unsafe suffix +private inline fun ByteArray.encodeToStringUnsafe(encoder: Encoder<*>, offset: Int, len: Int): String { + val maxSize = encoder.config.encodeOutMaxSize(len) + val sb = StringBuilder(maxSize) + encoder.encodeUnsafe(this, offset, len, _outFeed = { OutFeed(sb::append) }) + val result = sb.toString() + if (encoder.config.backFillBuffers) { + sb.wipe() + } + return result +} + +// Does not check offset/len, thus the *Unsafe suffix +private inline fun ByteArray.encodeToCharArrayUnsafe(encoder: Encoder<*>, offset: Int, len: Int): CharArray { + val maxSize = encoder.config.encodeOutMaxSize(len) + var i = 0 + val a = CharArray(maxSize) + encoder.encodeUnsafe(this, offset, len, _outFeed = { OutFeed { c -> a[i++] = c } }) + if (i == maxSize) return a + val copy = a.copyOf(i) + if (encoder.config.backFillBuffers) { + a.fill('\u0000', 0, i) + } + return copy +} diff --git a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Encoder.kt b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Encoder.kt index ebd240f..39c0758 100644 --- a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Encoder.kt +++ b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Encoder.kt @@ -26,27 +26,27 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract +// Does not check offset/len, thus the *Unsafe suffix @OptIn(ExperimentalContracts::class) -internal inline fun Encoder.encode( +internal inline fun Encoder.encodeUnsafe( data: ByteArray, + offset: Int, + len: Int, _outFeed: () -> Encoder.OutFeed, ) { contract { callsInPlace(_outFeed, InvocationKind.AT_MOST_ONCE) } - if (data.isEmpty()) return - encode(data, out = _outFeed()) -} - -internal inline fun Encoder.encode( - data: ByteArray, - out: Encoder.OutFeed, -) { - newEncoderFeed(out).use { feed -> data.forEach(feed::consume) } + if (len == 0) return + @Suppress("DEPRECATION") + encodeToUnsafe(data, offset, len, out = _outFeed()) } +// Does not check offset/len, thus the *Unsafe suffix @OptIn(ExperimentalContracts::class) @Throws(EncodingException::class) -internal inline fun Encoder.encodeBuffered( +internal inline fun Encoder.encodeBufferedUnsafe( data: ByteArray, + offset: Int, + len: Int, buf: CharArray?, maxBufSize: Int, throwOnOverflow: Boolean, @@ -67,10 +67,10 @@ internal inline fun Encoder.encodeBuffered( val parameter = if (buf != null) "buf.size" else "maxBufSize" "$parameter[$maxBufSize] <= ${this}.config.maxEncodeEmitWithLineBreak[${config.maxEncodeEmitWithLineBreak}]" } - if (data.isEmpty()) return 0L + if (len == 0) return 0L try { - config.encodeOutMaxSize(data.size) + config.encodeOutMaxSize(len) } catch (e: EncodingSizeException) { if (throwOnOverflow) throw e -1 @@ -80,7 +80,8 @@ internal inline fun Encoder.encodeBuffered( // Maximum encoded size will be less than or equal to maxBufSize. One-shot it. var i = 0 val encoded = buf ?: CharArray(maxEncodeSize) - encode(data, out = { c -> encoded[i++] = c }) + @Suppress("DEPRECATION") + encodeToUnsafe(data, offset, len, out = { c -> encoded[i++] = c }) try { _action(encoded, 0, i) } finally { @@ -96,8 +97,8 @@ internal inline fun Encoder.encodeBuffered( var size = 0L try { newEncoderFeed(out = { c -> _buf[iBuf++] = c }).use { feed -> - for (b in data) { - feed.consume(b) + for (i in 0 until len) { + feed.consume(data[offset + i]) if (iBuf <= limit) continue _action(_buf, 0, iBuf) size += iBuf @@ -112,3 +113,18 @@ internal inline fun Encoder.encodeBuffered( } return size } + +// Does not check offset/len, thus the *Unsafe suffix +@Deprecated("UNSAFE; do not reference directly. Used by encodeUnsafe/encodeBufferedUnsafe only.") +internal inline fun Encoder.encodeToUnsafe( + data: ByteArray, + offset: Int, + len: Int, + out: Encoder.OutFeed, +) { + newEncoderFeed(out).use { feed -> + repeat(len) { i -> + feed.consume(data[offset + i]) + } + } +} diff --git a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Util.kt b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Util.kt index 7bc0404..288ece2 100644 --- a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Util.kt +++ b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/internal/-Util.kt @@ -19,6 +19,9 @@ package io.matthewnelson.encoding.core.internal import io.matthewnelson.encoding.core.EncoderDecoder import io.matthewnelson.encoding.core.EncodingException +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract internal inline fun EncoderDecoder.Feed<*>.closedException(): EncodingException { return EncodingException("$this is closed") @@ -48,3 +51,23 @@ internal inline fun StringBuilder.commonWipe(len: Int, finalLen: Int): StringBui setLength(finalLen) return this } + +@Throws(IndexOutOfBoundsException::class) +internal inline fun ByteArray.checkBounds(offset: Int, len: Int) { size.checkBounds(offset, len) { "size" } } +@Throws(IndexOutOfBoundsException::class) +internal inline fun CharArray.checkBounds(offset: Int, len: Int) { size.checkBounds(offset, len) { "size" } } +@Throws(IndexOutOfBoundsException::class) +internal inline fun CharSequence.checkBounds(offset: Int, len: Int) { length.checkBounds(offset, len) { "length" } } + +@OptIn(ExperimentalContracts::class) +@Throws(IndexOutOfBoundsException::class) +internal inline fun Int.checkBounds(offset: Int, len: Int, paramName: () -> String) { + contract { callsInPlace(paramName, InvocationKind.AT_MOST_ONCE) } + val size = this + if (offset < 0) throw IndexOutOfBoundsException("offset[$offset] < 0") + if (len < 0) throw IndexOutOfBoundsException("len[$len] < 0") + if (offset > size - len) { + val parameter = paramName() + throw IndexOutOfBoundsException("offset[$offset] > $parameter[$size] - len[$len]") + } +} diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/BufferedDecodingUnitTest.kt similarity index 99% rename from library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt rename to library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/BufferedDecodingUnitTest.kt index a60ecaf..d0ac8d2 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/BufferedDecodingUnitTest.kt @@ -24,7 +24,7 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.fail -class DecodeBufferedUnitTest { +class BufferedDecodingUnitTest { @Test fun givenBufferSize_whenLessThanOrEqualToConfigMaxDecodeEmit_thenThrowsIllegalArgumentException() { diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncodeBufferedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/BufferedEncodingUnitTest.kt similarity index 98% rename from library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncodeBufferedUnitTest.kt rename to library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/BufferedEncodingUnitTest.kt index bab1b52..a0c1383 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncodeBufferedUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/BufferedEncodingUnitTest.kt @@ -15,7 +15,6 @@ **/ package io.matthewnelson.encoding.core -import io.matthewnelson.encoding.core.Decoder.Companion.decodeBuffered import io.matthewnelson.encoding.core.Encoder.Companion.encodeBuffered import io.matthewnelson.encoding.core.EncoderDecoder.Companion.DEFAULT_BUFFER_SIZE import io.matthewnelson.encoding.core.helpers.TestConfig @@ -24,7 +23,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith -class EncodeBufferedUnitTest { +class BufferedEncodingUnitTest { @Test fun givenBufferSize_whenLessThanOrEqualToConfigMaxEncodeEmitWithLineBreak_thenThrowsIllegalArgumentException() { diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/PartialEncodingUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/PartialEncodingUnitTest.kt new file mode 100644 index 0000000..5f9d118 --- /dev/null +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/PartialEncodingUnitTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 Matthew Nelson + * + * 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 + * + * https://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. + **/ +package io.matthewnelson.encoding.core + +import io.matthewnelson.encoding.core.Encoder.Companion.encodeBuffered +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToCharArray +import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import io.matthewnelson.encoding.core.helpers.TestConfig +import io.matthewnelson.encoding.core.helpers.TestEncoderDecoder +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotEquals + +class PartialEncodingUnitTest { + + @Test + fun givenEncode_whenPartialEncoding_thenChecksBounds() { + val encoder = TestEncoderDecoder(TestConfig()) + assertFailsWith { + ByteArray(1).encodeToString(encoder, -1, 1) + } + assertFailsWith { + ByteArray(1).encodeToCharArray(encoder, -1, 1) + } + } + + @Test + fun givenEncodeBuffered_whenPartialEncoding_thenChecksBounds() { + val encoder = TestEncoderDecoder(TestConfig()) + assertFailsWith { + ByteArray(1).encodeBuffered( + encoder, + true, + offset = -1, + len = 1, + maxBufSize = 25, + action = { _, _, _ -> error("Should not make it here") } + ) + } + assertFailsWith { + ByteArray(1).encodeBuffered( + encoder, + true, + offset = -1, + len = 1, + buf = CharArray(25), + action = { _, _, _ -> error("Should not make it here") } + ) + } + } + + @Test + fun givenEncode_whenPartialEncoding_thenConsumesCorrectIndices() { + val expected = 4 + var invocationEncodeOut = 0 + var invocationEncoderConsume = 0 + val encoder = TestEncoderDecoder( + config = TestConfig( + encodeOutReturn = { size -> + invocationEncodeOut++ + assertEquals(expected.toLong(), size) + size + }, + ), + encoderConsume = { b -> + invocationEncoderConsume++ + assertEquals(expected.toByte(), b) + output('b') + }, + encoderDoFinal = {}, + ) + + val data = byteArrayOf(0, expected.toByte(), expected.toByte(), expected.toByte(), expected.toByte(), 0) + assertEquals("bbbb", data.encodeToString(encoder, 1, expected)) + assertEquals(1, invocationEncodeOut) + assertEquals(expected, invocationEncoderConsume) + + assertContentEquals(charArrayOf('b', 'b', 'b', 'b'), data.encodeToCharArray(encoder, 1, expected)) + assertEquals(2, invocationEncodeOut) + assertEquals(expected * 2, invocationEncoderConsume) + + var invocationAction = 0 + // single shot encode + var result = data.encodeBuffered( + encoder, + throwOnOverflow = true, + offset = 1, + len = expected, + action = { buf, offset, len -> + invocationAction++ + assertEquals(expected, len) + assertEquals(0, offset) + repeat(len) { i -> assertEquals('b', buf[i]) } + } + ) + assertEquals(expected.toLong(), result) + assertEquals(1, invocationAction) + assertEquals(3, invocationEncodeOut) + assertEquals(expected * 3, invocationEncoderConsume) + invocationAction = 0 // reset + + // stream to buffer & flush + result = data.encodeBuffered( + encoder, + throwOnOverflow = true, + offset = 1, + len = expected, + maxBufSize = 3, + action = { buf, offset, len -> + invocationAction++ + assertEquals(0, offset) + assertNotEquals(0, len) + repeat(len) { i -> assertEquals('b', buf[i]) } + } + ) + assertEquals(expected.toLong(), result) + assertEquals(2, invocationAction) + assertEquals(4, invocationEncodeOut) + assertEquals(expected * 4, invocationEncoderConsume) + } +} diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/internal/CheckBoundsUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/internal/CheckBoundsUnitTest.kt new file mode 100644 index 0000000..fff7281 --- /dev/null +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/internal/CheckBoundsUnitTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 Matthew Nelson + * + * 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 + * + * https://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. + **/ +package io.matthewnelson.encoding.core.internal + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +class CheckBoundsUnitTest { + + @Test + fun givenSize_whenNegativeOffset_thenThrowsIndexOutOfBoundsException() { + try { + 2.checkBounds(-1, 1) { "..." } + fail() + } catch (e: IndexOutOfBoundsException) { + assertEquals(true, e.message?.contains("offset[")) + assertEquals(true, e.message?.contains("< 0")) + } + } + + @Test + fun givenSize_whenNegativeLen_thenThrowsIndexOutOfBoundsException() { + try { + 2.checkBounds(1, -1) { "..." } + fail() + } catch (e: IndexOutOfBoundsException) { + assertEquals(true, e.message?.contains("len[")) + assertEquals(true, e.message?.contains("< 0")) + } + } + + @Test + fun givenSize_whenOffsetExceedsBounds_thenThrowsIndexOutOfBoundsException() { + try { + 2.checkBounds(3, 0) { "PARAMETER NAME" } + fail() + } catch (e: IndexOutOfBoundsException) { + assertEquals(true, e.message?.contains("PARAMETER NAME")) + } + } + + @Test + fun givenSize_whenLenExceedsBounds_thenThrowsIndexOutOfBoundsException() { + try { + 2.checkBounds(0, 3) { "PARAMETER NAME" } + fail() + } catch (e: IndexOutOfBoundsException) { + assertEquals(true, e.message?.contains("PARAMETER NAME")) + } + } +}