diff --git a/library/core/README.md b/library/core/README.md index f21afb5..4a8adb5 100644 --- a/library/core/README.md +++ b/library/core/README.md @@ -58,7 +58,11 @@ fun main() { // Write UTF-8 encoded bytes to a FileStream (kmp-file:file) val file = "/path/to/file.txt".toFile() file.openWrite(excl = null).use { stream -> - decoded.decodeBuffered(UTF8, action = stream::write) + decoded.decodeBuffered( + decoder = UTF8, + throwOnOverflow = false, + action = stream::write, + ) } // Now do it asynchronously (kmp-file:async) @@ -67,8 +71,9 @@ fun main() { file.openAppendAsync(excl = OpenExcl.MustExist) .useAsync { stream -> decoded.decodeBufferedAsync( - maxBufSize = 1024, decoder = UTF8.ThrowOnInvalid, + throwOnOverflow = false, + maxBufSize = 1024, action = stream::writeAsync, ) } diff --git a/library/core/api/core.api b/library/core/api/core.api index e11157c..37a07fb 100644 --- a/library/core/api/core.api +++ b/library/core/api/core.api @@ -1,6 +1,7 @@ public abstract class io/matthewnelson/encoding/core/Decoder { public static final field Companion Lio/matthewnelson/encoding/core/Decoder$Companion; public synthetic fun (Lio/matthewnelson/encoding/core/EncoderDecoder$Config;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public static final fun decodeBuffered (Ljava/lang/CharSequence;ILio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function3;)J public static final fun decodeBuffered (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function3;)J public static final fun decodeBuffered (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZILkotlin/jvm/functions/Function3;)J public static final fun decodeBuffered (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZLkotlin/jvm/functions/Function3;)J @@ -8,6 +9,7 @@ public abstract class io/matthewnelson/encoding/core/Decoder { public static final fun decodeBuffered ([CLio/matthewnelson/encoding/core/Decoder;ZILkotlin/jvm/functions/Function3;)J public static final fun decodeBuffered ([CLio/matthewnelson/encoding/core/Decoder;ZLkotlin/jvm/functions/Function3;)J public static final fun decodeBuffered ([CLio/matthewnelson/encoding/core/Decoder;Z[BLkotlin/jvm/functions/Function3;)J + public static final fun decodeBufferedAsync (Ljava/lang/CharSequence;ILio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun decodeBufferedAsync (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun decodeBufferedAsync (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun decodeBufferedAsync (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -27,6 +29,7 @@ public abstract class io/matthewnelson/encoding/core/Decoder { } public final class io/matthewnelson/encoding/core/Decoder$Companion { + public final fun decodeBuffered (Ljava/lang/CharSequence;ILio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function3;)J public final fun decodeBuffered (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function3;)J public final fun decodeBuffered (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZILkotlin/jvm/functions/Function3;)J public final fun decodeBuffered (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZLkotlin/jvm/functions/Function3;)J @@ -34,6 +37,7 @@ public final class io/matthewnelson/encoding/core/Decoder$Companion { public final fun decodeBuffered ([CLio/matthewnelson/encoding/core/Decoder;ZILkotlin/jvm/functions/Function3;)J public final fun decodeBuffered ([CLio/matthewnelson/encoding/core/Decoder;ZLkotlin/jvm/functions/Function3;)J public final fun decodeBuffered ([CLio/matthewnelson/encoding/core/Decoder;Z[BLkotlin/jvm/functions/Function3;)J + public final fun decodeBufferedAsync (Ljava/lang/CharSequence;ILio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun decodeBufferedAsync (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;Lkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun decodeBufferedAsync (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZILkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun decodeBufferedAsync (Ljava/lang/CharSequence;Lio/matthewnelson/encoding/core/Decoder;ZLkotlin/jvm/functions/Function4;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -73,6 +77,12 @@ 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;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;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 encodeToString ([BLio/matthewnelson/encoding/core/Encoder;)Ljava/lang/String; @@ -81,6 +91,12 @@ public abstract class io/matthewnelson/encoding/core/Encoder : io/matthewnelson/ } public final class io/matthewnelson/encoding/core/Encoder$Companion { + 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;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 encodeToString ([BLio/matthewnelson/encoding/core/Encoder;)Ljava/lang/String; @@ -129,6 +145,7 @@ public abstract class io/matthewnelson/encoding/core/EncoderDecoder$Config { public final field lineBreakResetOnFlush Z public final field maxDecodeEmit I public final field maxEncodeEmit I + public final field maxEncodeEmitWithLineBreak I public final field paddingChar Ljava/lang/Character; public fun (Ljava/lang/Boolean;BLjava/lang/Character;)V protected fun (Ljava/lang/Boolean;BZLjava/lang/Character;IIZ)V @@ -137,6 +154,8 @@ public abstract class io/matthewnelson/encoding/core/EncoderDecoder$Config { public final fun decodeOutMaxSizeOrFail (Lio/matthewnelson/encoding/core/util/DecoderInput;)I protected abstract fun decodeOutMaxSizeOrFailProtected (ILio/matthewnelson/encoding/core/util/DecoderInput;)I protected abstract fun decodeOutMaxSizeProtected (J)J + public final fun encodeOutMaxSize (I)I + public final fun encodeOutMaxSize (IB)I public final fun encodeOutMaxSize (J)J public final fun encodeOutMaxSize (JB)J public final fun encodeOutSize (J)J diff --git a/library/core/api/core.klib.api b/library/core/api/core.klib.api index 6019ab4..dd9c495 100644 --- a/library/core/api/core.klib.api +++ b/library/core/api/core.klib.api @@ -51,6 +51,8 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat final fun (): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxDecodeEmit.|(){}[0] final val maxEncodeEmit // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmit|{}maxEncodeEmit[0] final fun (): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmit.|(){}[0] + final val maxEncodeEmitWithLineBreak // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmitWithLineBreak|{}maxEncodeEmitWithLineBreak[0] + final fun (): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmitWithLineBreak.|(){}[0] final val paddingChar // io.matthewnelson.encoding.core/EncoderDecoder.Config.paddingChar|{}paddingChar[0] final fun (): kotlin/Char? // io.matthewnelson.encoding.core/EncoderDecoder.Config.paddingChar.|(){}[0] @@ -60,6 +62,7 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat abstract fun toStringAddSettings(): kotlin.collections/Set // io.matthewnelson.encoding.core/EncoderDecoder.Config.toStringAddSettings|toStringAddSettings(){}[0] final fun decodeOutMaxSize(kotlin/Long): kotlin/Long // io.matthewnelson.encoding.core/EncoderDecoder.Config.decodeOutMaxSize|decodeOutMaxSize(kotlin.Long){}[0] final fun decodeOutMaxSizeOrFail(io.matthewnelson.encoding.core.util/DecoderInput): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.decodeOutMaxSizeOrFail|decodeOutMaxSizeOrFail(io.matthewnelson.encoding.core.util.DecoderInput){}[0] + final fun encodeOutMaxSize(kotlin/Int, kotlin/Byte): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.encodeOutMaxSize|encodeOutMaxSize(kotlin.Int;kotlin.Byte){}[0] final fun encodeOutMaxSize(kotlin/Long): kotlin/Long // io.matthewnelson.encoding.core/EncoderDecoder.Config.encodeOutMaxSize|encodeOutMaxSize(kotlin.Long){}[0] final fun encodeOutMaxSize(kotlin/Long, kotlin/Byte): kotlin/Long // io.matthewnelson.encoding.core/EncoderDecoder.Config.encodeOutMaxSize|encodeOutMaxSize(kotlin.Long;kotlin.Byte){}[0] final fun encodeOutSize(kotlin/Long): kotlin/Long // io.matthewnelson.encoding.core/EncoderDecoder.Config.encodeOutSize|encodeOutSize(kotlin.Long){}[0] @@ -67,6 +70,7 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat final fun equals(kotlin/Any?): kotlin/Boolean // io.matthewnelson.encoding.core/EncoderDecoder.Config.equals|equals(kotlin.Any?){}[0] final fun hashCode(): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.hashCode|hashCode(){}[0] final fun toString(): kotlin/String // io.matthewnelson.encoding.core/EncoderDecoder.Config.toString|toString(){}[0] + final inline fun encodeOutMaxSize(kotlin/Int): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.encodeOutMaxSize|encodeOutMaxSize(kotlin.Int){}[0] final inner class Setting { // io.matthewnelson.encoding.core/EncoderDecoder.Config.Setting|null[0] constructor (kotlin/String, kotlin/Any?) // io.matthewnelson.encoding.core/EncoderDecoder.Config.Setting.|(kotlin.String;kotlin.Any?){}[0] @@ -223,6 +227,7 @@ sealed class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.matth final fun (kotlin/CharArray).decodeToByteArrayOrNull(io.matthewnelson.encoding.core/Decoder<*>): kotlin/ByteArray? // io.matthewnelson.encoding.core/Decoder.Companion.decodeToByteArrayOrNull|decodeToByteArrayOrNull@kotlin.CharArray(io.matthewnelson.encoding.core.Decoder<*>){}[0] final fun (kotlin/CharSequence).decodeBuffered(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, kotlin/ByteArray, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBuffered|decodeBuffered@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.ByteArray;kotlin.Function3){}[0] final fun (kotlin/CharSequence).decodeBuffered(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, kotlin/Int, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBuffered|decodeBuffered@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.Int;kotlin.Function3){}[0] + final fun (kotlin/CharSequence).decodeBuffered(kotlin/Int, io.matthewnelson.encoding.core/Decoder<*>, kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBuffered|decodeBuffered@kotlin.CharSequence(kotlin.Int;io.matthewnelson.encoding.core.Decoder<*>;kotlin.Function3){}[0] final fun (kotlin/CharSequence).decodeToByteArray(io.matthewnelson.encoding.core/Decoder<*>): kotlin/ByteArray // io.matthewnelson.encoding.core/Decoder.Companion.decodeToByteArray|decodeToByteArray@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>){}[0] final fun (kotlin/CharSequence).decodeToByteArrayOrNull(io.matthewnelson.encoding.core/Decoder<*>): kotlin/ByteArray? // io.matthewnelson.encoding.core/Decoder.Companion.decodeToByteArrayOrNull|decodeToByteArrayOrNull@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>){}[0] final inline fun (kotlin/CharArray).decodeBuffered(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, noinline kotlin/Function3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBuffered|decodeBuffered@kotlin.CharArray(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.Function3){}[0] @@ -232,6 +237,7 @@ sealed class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.matth final suspend fun (kotlin/CharArray).decodeBufferedAsync(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, kotlin/Int, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharArray(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.Int;kotlin.coroutines.SuspendFunction3){}[0] final suspend fun (kotlin/CharSequence).decodeBufferedAsync(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, kotlin/ByteArray, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.ByteArray;kotlin.coroutines.SuspendFunction3){}[0] final suspend fun (kotlin/CharSequence).decodeBufferedAsync(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, kotlin/Int, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.Int;kotlin.coroutines.SuspendFunction3){}[0] + final suspend fun (kotlin/CharSequence).decodeBufferedAsync(kotlin/Int, io.matthewnelson.encoding.core/Decoder<*>, kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharSequence(kotlin.Int;io.matthewnelson.encoding.core.Decoder<*>;kotlin.coroutines.SuspendFunction3){}[0] final suspend inline fun (kotlin/CharArray).decodeBufferedAsync(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, noinline kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharArray(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.coroutines.SuspendFunction3){}[0] final suspend inline fun (kotlin/CharSequence).decodeBufferedAsync(io.matthewnelson.encoding.core/Decoder<*>, kotlin/Boolean, noinline kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>;kotlin.Boolean;kotlin.coroutines.SuspendFunction3){}[0] final suspend inline fun (kotlin/CharSequence).decodeBufferedAsync(io.matthewnelson.encoding.core/Decoder<*>, noinline kotlin.coroutines/SuspendFunction3): kotlin/Long // io.matthewnelson.encoding.core/Decoder.Companion.decodeBufferedAsync|decodeBufferedAsync@kotlin.CharSequence(io.matthewnelson.encoding.core.Decoder<*>;kotlin.coroutines.SuspendFunction3){}[0] @@ -268,9 +274,15 @@ 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).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).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 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 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 a974385..265f4b9 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 @@ -338,7 +338,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @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 * at index `offset`, to "write". @@ -404,7 +404,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [maxBufSize] The maximum size array this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The function to flush the buffer to; a destination to "write" @@ -419,10 +419,10 @@ public sealed class Decoder(public val config: C) { * * @throws [EncodingException] If decoding failed, such as the [decoder] rejecting * an invalid character or sequence. - * @throws [IllegalArgumentException] If [maxBufSize] is less than or equal to - * [EncoderDecoder.Config.maxDecodeEmit]. * @throws [EncodingSizeException] If [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * threw its exception and [throwOnOverflow] is `true`. + * @throws [IllegalArgumentException] If [maxBufSize] is less than or equal to + * [EncoderDecoder.Config.maxDecodeEmit]. * */ @JvmStatic @Throws(EncodingException::class) @@ -490,7 +490,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [buf] The pre-allocated array to use as the buffer. Its size must be * greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The function to flush the buffer to; a destination to "write" @@ -569,7 +569,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @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`, * starting at index `offset`, to "write". @@ -638,7 +638,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [maxBufSize] The maximum size array this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The suspend function to flush the buffer to; a destination to @@ -728,7 +728,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [buf] The pre-allocated array to use as the buffer. Its size must be * greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The suspend function to flush the buffer to; a destination to @@ -809,7 +809,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @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 * at index `offset`, to "write". @@ -877,7 +877,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [maxBufSize] The maximum size array this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The function to flush the buffer to; a destination to "write" @@ -967,7 +967,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [buf] The pre-allocated array to use as the buffer. Its size must be * greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The function to flush the buffer to; a destination to "write" @@ -1047,7 +1047,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @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`, * starting at index `offset`, to "write". @@ -1117,7 +1117,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [maxBufSize] The maximum size array this function will allocate. Must * be greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The suspend function to flush the buffer to; a destination to @@ -1208,7 +1208,7 @@ public sealed class Decoder(public val config: C) { * @param [decoder] The [Decoder] to use. * @param [throwOnOverflow] If `true` and [EncoderDecoder.Config.decodeOutMaxSizeOrFail] * throws an [EncodingSizeException], it will be re-thrown. If `false`, the exception - * will be swallowed and stream decoding to the buffer will continue. + * will be ignored and stream decoding to the buffer will continue. * @param [buf] The pre-allocated array to use as the buffer. Its size must be * greater than [EncoderDecoder.Config.maxDecodeEmit]. * @param [action] The suspend function to flush the buffer to; a destination to @@ -1260,6 +1260,22 @@ public sealed class Decoder(public val config: C) { noinline action: (buf: ByteArray, offset: Int, len: Int) -> Unit, ): Long = decodeBuffered(decoder, false, DEFAULT_BUFFER_SIZE, action) + /** + * TODO: Remove. See https://github.com/05nelsonm/encoding/issues/225 + * @suppress + * */ + @JvmStatic + @Throws(EncodingException::class) + @Deprecated( + message = "Will be removed upon 2.6.0 release", + replaceWith = ReplaceWith("decodeBuffered(decoder, false, maxBufSize, action)") + ) + public fun CharSequence.decodeBuffered( + maxBufSize: Int, + decoder: Decoder<*>, + action: (buf: ByteArray, offset: Int, len: Int) -> Unit, + ): Long = decodeBuffered(decoder, false, maxBufSize, action) + /** * TODO: Remove. See https://github.com/05nelsonm/encoding/issues/225 * @suppress @@ -1275,6 +1291,22 @@ public sealed class Decoder(public val config: C) { noinline action: suspend (buf: ByteArray, offset: Int, len: Int) -> Unit, ): Long = decodeBufferedAsync(decoder, false, DEFAULT_BUFFER_SIZE, action) + /** + * TODO: Remove. See https://github.com/05nelsonm/encoding/issues/225 + * @suppress + * */ + @JvmStatic + @Throws(CancellationException::class, EncodingException::class) + @Deprecated( + message = "Will be removed upon 2.6.0 release", + replaceWith = ReplaceWith("decodeBufferedAsync(decoder, false, maxBufSize, action)") + ) + public suspend fun CharSequence.decodeBufferedAsync( + maxBufSize: Int, + decoder: Decoder<*>, + action: suspend (buf: ByteArray, offset: Int, len: Int) -> Unit, + ): Long = decodeBufferedAsync(decoder, false, maxBufSize, action) + /** * DEPRECATED since `2.3.0` * @throws [EncodingException] If decoding failed, such as the [decoder] rejecting 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 b5a8c4f..3753322 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 @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -@file:Suppress("LocalVariableName", "PropertyName", "RemoveRedundantQualifierName", "RedundantVisibilityModifier") +@file:Suppress("LocalVariableName", "NOTHING_TO_INLINE", "PropertyName", "RemoveRedundantQualifierName", "RedundantVisibilityModifier") package io.matthewnelson.encoding.core +import io.matthewnelson.encoding.core.EncoderDecoder.Companion.DEFAULT_BUFFER_SIZE import io.matthewnelson.encoding.core.internal.closedException import io.matthewnelson.encoding.core.internal.encode -import io.matthewnelson.encoding.core.internal.encodeOutMaxSizeOrFail +import io.matthewnelson.encoding.core.internal.encodeBuffered import io.matthewnelson.encoding.core.util.LineBreakOutFeed import io.matthewnelson.encoding.core.util.wipe +import kotlin.coroutines.cancellation.CancellationException import kotlin.jvm.JvmField import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic @@ -33,6 +35,8 @@ import kotlin.jvm.JvmSynthetic * @see [EncoderDecoder] * @see [encodeToString] * @see [encodeToCharArray] + * @see [encodeBuffered] + * @see [encodeBufferedAsync] * @see [Encoder.Feed] * */ public sealed class Encoder(config: C): Decoder(config) { @@ -212,22 +216,24 @@ public sealed class Encoder(config: C): Decoder(con * @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 the encoded output would exceed [Int.MAX_VALUE]. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * throws an exception (i.e. output would exceed [Int.MAX_VALUE]). * */ @JvmStatic public fun ByteArray.encodeToString(encoder: Encoder<*>): String { - return encoder.encodeOutMaxSizeOrFail(size) { maxSize -> - val sb = StringBuilder(maxSize) - encoder.encode(this, _outFeed = { OutFeed(sb::append) }) - val result = sb.toString() - if (encoder.config.backFillBuffers) { - sb.wipe() - } - result + 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 } /** @@ -238,26 +244,390 @@ public sealed class Encoder(config: C): Decoder(con * @return The [CharArray] of encoded data. * * @see [encodeToString] + * @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 the encoded output exceeds [Int.MAX_VALUE]. + * @throws [EncodingSizeException] If [EncoderDecoder.Config.encodeOutMaxSize] + * throws an exception (i.e. output would exceed [Int.MAX_VALUE]). * */ @JvmStatic public fun ByteArray.encodeToCharArray(encoder: Encoder<*>): CharArray { - return encoder.encodeOutMaxSizeOrFail(size) block@ { maxSize -> - var i = 0 - val a = CharArray(maxSize) - encoder.encode(this, _outFeed = { OutFeed { c -> a[i++] = c } }) - if (i == maxSize) return@block a - val copy = a.copyOf(i) - if (encoder.config.backFillBuffers) { - a.fill('\u0000', 0, i) - } - copy + 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 } + /** + * Encode a [ByteArray] using a maximum array size of [DEFAULT_BUFFER_SIZE]. + * The encoding operation will allocate a single array, 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 + * 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 [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`. + * */ + @JvmStatic + public inline fun ByteArray.encodeBuffered( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + noinline action: (buf: CharArray, offset: Int, len: Int) -> Unit, + ): 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 + * 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 + * 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 [maxBufSize] The maximum size array 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]. + * */ + @JvmStatic + public fun ByteArray.encodeBuffered( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + maxBufSize: Int, + action: (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long = encoder.encodeBuffered( + data = this, + 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, + * 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 [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]. + * */ + @JvmStatic + public fun ByteArray.encodeBuffered( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + buf: CharArray, + action: (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long = encoder.encodeBuffered( + data = this, + buf = buf, + maxBufSize = buf.size, + throwOnOverflow = throwOnOverflow, + _action = action, + ) + + /** + * Encode a [ByteArray] using a maximum array size of [DEFAULT_BUFFER_SIZE]. + * The encoding operation will allocate a single array, 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 + * 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 [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`. + * and [throwOnOverflow] is `true`. + * */ + @JvmStatic + public suspend inline fun ByteArray.encodeBufferedAsync( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + noinline action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, + ): 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 + * 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 + * 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 [maxBufSize] The maximum size array 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]. + * */ + @JvmStatic + public suspend fun ByteArray.encodeBufferedAsync( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + maxBufSize: Int, + action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long = encoder.encodeBuffered( + data = this, + 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, + * 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 [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]. + * */ + @JvmStatic + public suspend fun ByteArray.encodeBufferedAsync( + encoder: Encoder<*>, + throwOnOverflow: Boolean, + buf: CharArray, + action: suspend (buf: CharArray, offset: Int, len: Int) -> Unit, + ): Long = encoder.encodeBuffered( + data = this, + 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, @@ -271,17 +641,16 @@ public sealed class Encoder(config: C): Decoder(con level = DeprecationLevel.ERROR, ) public fun ByteArray.encodeToByteArray(encoder: Encoder<*>): ByteArray { - return encoder.encodeOutMaxSizeOrFail(size) block@ { maxSize -> - var i = 0 - val a = ByteArray(maxSize) - encoder.encode(this, _outFeed = { OutFeed { char -> a[i++] = char.code.toByte() } }) - if (i == maxSize) return@block a - val copy = a.copyOf(i) - if (encoder.config.backFillBuffers) { - a.fill(0, 0, i) - } - copy + val maxSize = encoder.config.encodeOutMaxSize(size) + var i = 0 + val a = ByteArray(maxSize) + encoder.encode(this, _outFeed = { OutFeed { char -> a[i++] = char.code.toByte() } }) + if (i == maxSize) return a + val copy = a.copyOf(i) + if (encoder.config.backFillBuffers) { + a.fill(0, 0, i) } + return copy } } } diff --git a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt index 82c097c..ee7a173 100644 --- a/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt +++ b/library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -@file:Suppress("RedundantVisibilityModifier", "RemoveRedundantQualifierName") +@file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "RemoveRedundantQualifierName") package io.matthewnelson.encoding.core @@ -49,7 +49,7 @@ public abstract class EncoderDecoder(config: C): Encod /** * Base configuration for an [EncoderDecoder]. More options may be specified by the implementation. * */ - public abstract class Config private constructor( + public abstract class Config { /** * If `true`, the characters ('\n', '\r', ' ', '\t') will be skipped over (i.e. allowed @@ -58,7 +58,7 @@ public abstract class EncoderDecoder(config: C): Encod * are passed along to the [Decoder.Feed] implementation as input. * */ @JvmField - public val isLenient: Boolean?, + public val isLenient: Boolean? /** * If greater than `0`, [Encoder.newEncoderFeed] may use a [LineBreakOutFeed] such that @@ -70,19 +70,19 @@ public abstract class EncoderDecoder(config: C): Encod * @see [Encoder.newEncoderFeed] * */ @JvmField - public val lineBreakInterval: Byte, + public val lineBreakInterval: Byte /** * If and only if [Encoder.newEncoderFeed] wraps the [Encoder.OutFeed] passed to it with a - * [LineBreakOutFeed], will this setting be used for [LineBreakOutFeed.resetOnFlush]. If - * `true` and [Encoder.newEncoderFeed] wrapped its provided [Encoder.OutFeed], then - * [LineBreakOutFeed.reset] will be called after every invocation of [Encoder.Feed.flush]. + * [LineBreakOutFeed], will this setting then be used for the [LineBreakOutFeed.resetOnFlush] + * parameter. If `true` and [Encoder.newEncoderFeed] wrapped its provided [Encoder.OutFeed], + * then [LineBreakOutFeed.reset] will be called after every invocation of [Encoder.Feed.flush]. * * @see [LineBreakOutFeed] * @see [Encoder.newEncoderFeed] * */ @JvmField - public val lineBreakResetOnFlush: Boolean, + public val lineBreakResetOnFlush: Boolean /** * The character that is used when padding encoded output. This is used by [Decoder.Feed] @@ -95,7 +95,7 @@ public abstract class EncoderDecoder(config: C): Encod * specifying `null` and managing it in the implementation. * */ @JvmField - public val paddingChar: Char?, + public val paddingChar: Char? /** * The maximum number of bytes that the implementation's [Decoder.Feed] can potentially @@ -105,7 +105,7 @@ public abstract class EncoderDecoder(config: C): Encod * For example, `Base16` decoding will emit `1` byte for every `2` characters of input, * so its maximum emission is `1`. `Base32` decoding will emit `5` bytes for every `8` * characters of input, so its maximum emission is `5`. `UTF8` "decoding" (i.e. text to - * UTF-8 byte transformations) can emit `4` bytes, but also depending on the size of the + * `UTF-8` byte transformations) can emit `4` bytes, but also depending on the size of the * replacement byte sequence being used, can emit more; its maximum emission size needs * a calculation, such as `(replacementStrategy.size * 2).coerceAtLeast(4)`. * @@ -115,7 +115,7 @@ public abstract class EncoderDecoder(config: C): Encod * on this value (such as [Decoder.decodeBuffered] and [Decoder.decodeBufferedAsync]). * */ @JvmField - public val maxDecodeEmit: Int, + public val maxDecodeEmit: Int /** * The maximum number of characters that the implementation's [Encoder.Feed] can @@ -124,7 +124,7 @@ public abstract class EncoderDecoder(config: C): Encod * * For example, `Base16` encoding will emit `2` characters for every `1` byte of input, * so its maximum emission is `2`. `Base32` encoding will emit `8` characters for every - * `5` bytes of input, so its maximum emission is `8`. `UTF8` "encoding" (i.e. UTF-8 byte + * `5` bytes of input, so its maximum emission is `8`. `UTF8` "encoding" (i.e. `UTF-8` byte * to text transformations) can emit `2` characters, but also depending on the strategy * being used for replacement sequences, can emit more; its maximum emission size needs * some logic, such as `if (replacementStrategy == ReplacementStrategy.THROW) 2 else 4`. @@ -138,39 +138,59 @@ public abstract class EncoderDecoder(config: C): Encod * Value will be between `1` and `255` (inclusive), or `-1` which indicates that the * [EncoderDecoder.Config] implementation has not updated to the new constructor introduced * in version `2.6.0`, and as such is unable to be used with `:core` module APIs dependent - * on this value. + * on this value (such as [Encoder.encodeBuffered] and [Encoder.encodeBufferedAsync]). * + * @see [maxEncodeEmitWithLineBreak] * @see [Companion.calculateMaxEncodeEmit] * */ @JvmField - public val maxEncodeEmit: Int, + public val maxEncodeEmit: Int + + /** + * The maximum number of characters that the [Encoder.Feed] produced by [Encoder.newEncoderFeed] + * can potentially emit on a single invocation of [Encoder.Feed.consume], [Encoder.Feed.flush], + * or [Encoder.Feed.doFinal]. This is the calculated result from [Companion.calculateMaxEncodeEmit] + * using [maxEncodeEmit] and [lineBreakInterval] as arguments. + * + * **NOTE:** This value is not applicable if passing as an argument to [Encoder.newEncoderFeed] + * your own [LineBreakOutFeed] that has been instantiated with a value than [lineBreakInterval]. + * Passing a custom stack of [Encoder.OutFeed] which would inflate the encoded output size must + * be calculated using [maxEncodeEmit] and [calculateMaxEncodeEmit]. + * + * Value will be between `1` and `510` (inclusive), or `-1` which indicates that the + * [EncoderDecoder.Config] implementation has not updated to the new constructor introduced + * in version `2.6.0`, and as such is unable to be used with `:core` module APIs dependent + * on this value (such as [Encoder.encodeBuffered] and [Encoder.encodeBufferedAsync]). + * + * @see [maxEncodeEmit] + * @see [Companion.calculateMaxEncodeEmit] + * */ + @JvmField + public val maxEncodeEmitWithLineBreak: Int /** * When the functions [Encoder.encodeToString], [Encoder.encodeToCharArray], - * [Decoder.decodeToByteArray], [Decoder.decodeBuffered], and [Decoder.decodeBufferedAsync] - * are utilized, they may allocate an appropriate medium (a buffer) to store encoded/decoded - * data (e.g. a [StringBuilder], [CharArray], or [ByteArray]). Depending on the underlying - * encoding/decoding operation, such as an array over-allocation due to [encodeOutMaxSize] - * or [decodeOutMaxSize], those initially allocated buffers may not be returned as the - * function's result. Prior versions of this library always back-filled them with `0` or the - * null character `\u0000`, but that can be computationally expensive for large datasets and - * potentially unnecessary if data is known to not be sensitive in nature. + * [Encoder.encodeBuffered], [Encoder.encodeBufferedAsync] [Decoder.decodeToByteArray], + * [Decoder.decodeBuffered], and [Decoder.decodeBufferedAsync] are utilized, they may + * allocate an appropriate medium (a buffer) to store encoded/decoded data (e.g. a + * [StringBuilder], [CharArray], or [ByteArray]). Depending on the underlying operation, + * such as an array over-allocation due to [encodeOutMaxSize] or [decodeOutMaxSize], + * those initially allocated buffers may not be returned as the function's result. Prior + * versions of this library always back-filled them with `0` or the null character + * `\u0000`, but that can be computationally expensive for large datasets and potentially + * unnecessary if data is known to not be sensitive in nature. * * If `true`, any non-result buffer allocations are back-filled before being de-referenced * by function return. If `false`, back-filling is skipped. * */ @JvmField - public val backFillBuffers: Boolean, - - // NOTE: Adding any parameters requires updating equals/hashCode/toString - @Suppress("UNUSED_PARAMETER") unused: Any?, - ) { + public val backFillBuffers: Boolean /** * Instantiates a new [Config] instance. * * @throws [IllegalArgumentException] If [maxDecodeEmit] is less than `1` or greater than - * `255`. If [maxEncodeEmit] is less than `1` or greater than `255`. + * `255`, or if [maxEncodeEmit] is less than `1` or greater than `255`. * */ protected constructor( isLenient: Boolean?, @@ -180,28 +200,28 @@ public abstract class EncoderDecoder(config: C): Encod maxDecodeEmit: Int, maxEncodeEmit: Int, backFillBuffers: Boolean, - ): this( - isLenient = isLenient, - lineBreakInterval = lineBreakIntervalOrZero(isLenient, lineBreakInterval), - lineBreakResetOnFlush = lineBreakResetOnFlush, - paddingChar = paddingChar, - maxDecodeEmit = maxDecodeEmit, - maxEncodeEmit = maxEncodeEmit, - backFillBuffers = backFillBuffers, - unused = null, ) { checkMaxEmitSize(maxDecodeEmit) { "maxDecodeEmit" } checkMaxEmitSize(maxEncodeEmit) { "maxEncodeEmit" } + this.isLenient = isLenient + this.lineBreakInterval = lineBreakIntervalOrZero(isLenient, lineBreakInterval) + this.lineBreakResetOnFlush = lineBreakResetOnFlush + this.paddingChar = paddingChar + this.maxDecodeEmit = maxDecodeEmit + this.maxEncodeEmit = maxEncodeEmit + this.maxEncodeEmitWithLineBreak = calculateMaxEncodeEmit(maxEncodeEmit, this.lineBreakInterval.toInt()) + this.backFillBuffers = backFillBuffers } /** - * Pre-calculates and returns the maximum size of the output, after encoding would occur, - * based off the [Config] options set for the implementation. Most implementations (such - * as `Base16`, `Base32`, and `Base64`) are able to return an exact size whereby no - * post-encoding resize is necessary, while others (such as `UTF-8`) return a maximum - * and may require a post-encoding resize. + * Pre-calculates and returns the maximum size of the output, after encoding + * would occur, based off the [Config] options set for the implementation. + * Most implementations, such as `Base16`, `Base32`, and `Base64`, are able + * to return an exact size whereby no post-encoding resize is necessary, while + * others, such as `UTF-8` byte to text transformations, return a maximum and + * may require a post-encoding resize. * - * Will always return a value greater than or equal to `0`. + * Will always return a value greater than or equal to `0L`. * * @param [unEncodedSize] The size of the data which is to be encoded. * @@ -212,13 +232,14 @@ public abstract class EncoderDecoder(config: C): Encod public fun encodeOutMaxSize(unEncodedSize: Long): Long = encodeOutMaxSize(unEncodedSize, lineBreakInterval) /** - * Pre-calculates and returns the maximum size of the output, after encoding would occur, - * based off the [Config] options set for the implementation and expressed [lineBreakInterval]. - * Most implementations (such as `Base16`, `Base32`, and `Base64`) are able to return an - * exact size whereby no post-encoding resize is necessary, while others (such as `UTF-8`) - * return a maximum and may require a post-encoding resize. + * Pre-calculates and returns the maximum size of the output, after encoding + * would occur, based off the [Config] options set for the implementation. + * Most implementations, such as `Base16`, `Base32`, and `Base64`, are able + * to return an exact size whereby no post-encoding resize is necessary, while + * others, such as `UTF-8` byte to text transformations, return a maximum and + * may require a post-encoding resize. * - * Will always return a value greater than or equal to `0`. + * Will always return a value greater than or equal to `0L`. * * @param [unEncodedSize] The size of the data which is to be encoded. * @param [lineBreakInterval] The interval at which new line characters are to be inserted. @@ -261,6 +282,47 @@ public abstract class EncoderDecoder(config: C): Encod return outSize } + /** + * Pre-calculates and returns the maximum size of the output, after encoding + * would occur, based off the [Config] options set for the implementation. + * Most implementations, such as `Base16`, `Base32`, and `Base64`, are able + * to return an exact size whereby no post-encoding resize is necessary, while + * others, such as `UTF-8` byte to text transformations, return a maximum and + * may require a post-encoding resize. + * + * Will always return a value greater than or equal to `0`. + * + * @param [unEncodedSize] The size of the data which is to be encoded. + * + * @throws [EncodingSizeException] If [unEncodedSize] is negative, or the calculated + * size exceeds [Int.MAX_VALUE]. + * */ + @Throws(EncodingSizeException::class) + public inline fun encodeOutMaxSize(unEncodedSize: Int): Int = encodeOutMaxSize(unEncodedSize, lineBreakInterval) + + /** + * Pre-calculates and returns the maximum size of the output, after encoding + * would occur, based off the [Config] options set for the implementation. + * Most implementations, such as `Base16`, `Base32`, and `Base64`, are able + * to return an exact size whereby no post-encoding resize is necessary, while + * others, such as `UTF-8` byte to text transformations, return a maximum and + * may require a post-encoding resize. + * + * Will always return a value greater than or equal to `0`. + * + * @param [unEncodedSize] The size of the data which is to be encoded. + * @param [lineBreakInterval] The interval at which new line characters are to be inserted. + * + * @throws [EncodingSizeException] If [unEncodedSize] is negative, or the calculated + * size exceeds [Int.MAX_VALUE]. + * */ + @Throws(EncodingSizeException::class) + public fun encodeOutMaxSize(unEncodedSize: Int, lineBreakInterval: Byte): Int { + val outSize = encodeOutMaxSize(unEncodedSize.toLong(), lineBreakInterval) + if (outSize <= Int.MAX_VALUE) return outSize.toInt() + throw outSizeExceedsMaxEncodingSizeException(unEncodedSize, Int.MAX_VALUE) + } + /** * Pre-calculates and returns the maximum size of the output, after * decoding would occur, for input that is not yet known (i.e. @@ -620,16 +682,16 @@ public abstract class EncoderDecoder(config: C): Encod isLenient: Boolean?, lineBreakInterval: Byte, paddingChar: Char?, - ): this( - isLenient = isLenient, - lineBreakInterval = lineBreakIntervalOrZero(isLenient, lineBreakInterval), - lineBreakResetOnFlush = false, - paddingChar = paddingChar, - maxDecodeEmit = -1, // NOTE: NEVER change. - maxEncodeEmit = -1, // NOTE: NEVER change. - backFillBuffers = true, - unused = null, - ) + ) { + this.isLenient = isLenient + this.lineBreakInterval = lineBreakIntervalOrZero(isLenient, lineBreakInterval) + this.lineBreakResetOnFlush = false + this.paddingChar = paddingChar + this.maxDecodeEmit = -1 // NOTE: NEVER change. + this.maxEncodeEmit = -1 // NOTE: NEVER change. + this.maxEncodeEmitWithLineBreak = -1 // NOTE: NEVER change. + this.backFillBuffers = true + } } /** @@ -787,7 +849,6 @@ public abstract class EncoderDecoder(config: C): Encod } } -@Suppress("NOTHING_TO_INLINE") private inline fun lineBreakIntervalOrZero(isLenient: Boolean?, interval: Byte): Byte { return if (isLenient != false && interval > 0) interval else 0 } @@ -806,7 +867,6 @@ private inline fun checkMaxEmitSize(size: Int, parameterName: () -> String) { } } -@Suppress("NOTHING_TO_INLINE") private inline fun negativeEncodingSizeException(outSize: Number): EncodingSizeException { return EncodingSizeException("Calculated output of Size[$outSize] was negative") } 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 b42841a..ebd240f 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 @@ -13,37 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -@file:Suppress("LocalVariableName") +@file:Suppress("LocalVariableName", "NOTHING_TO_INLINE") package io.matthewnelson.encoding.core.internal import io.matthewnelson.encoding.core.Encoder import io.matthewnelson.encoding.core.EncoderDecoder.Config +import io.matthewnelson.encoding.core.EncodingException import io.matthewnelson.encoding.core.EncodingSizeException import io.matthewnelson.encoding.core.use import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract -private const val MAX_ENCODE_OUT_SIZE: Long = Int.MAX_VALUE.toLong() - -/** - * Fails if the returned [Long] for [Config.encodeOutMaxSize] exceeds [Int.MAX_VALUE]. - * */ -@OptIn(ExperimentalContracts::class) -@Throws(EncodingSizeException::class) -internal inline fun Encoder<*>.encodeOutMaxSizeOrFail( - size: Int, - _block: (maxSize: Int) -> T, -): T { - contract { callsInPlace(_block, InvocationKind.AT_MOST_ONCE) } - val maxSize = config.encodeOutMaxSize(size.toLong()) - if (maxSize > MAX_ENCODE_OUT_SIZE) { - throw Config.outSizeExceedsMaxEncodingSizeException(maxSize, MAX_ENCODE_OUT_SIZE) - } - return _block(maxSize.toInt()) -} - @OptIn(ExperimentalContracts::class) internal inline fun Encoder.encode( data: ByteArray, @@ -51,6 +33,82 @@ internal inline fun Encoder.encode( ) { contract { callsInPlace(_outFeed, InvocationKind.AT_MOST_ONCE) } if (data.isEmpty()) return - val out = _outFeed() - newEncoderFeed(out).use { feed -> data.forEach { b -> feed.consume(b) } } + encode(data, out = _outFeed()) +} + +internal inline fun Encoder.encode( + data: ByteArray, + out: Encoder.OutFeed, +) { + newEncoderFeed(out).use { feed -> data.forEach(feed::consume) } +} + +@OptIn(ExperimentalContracts::class) +@Throws(EncodingException::class) +internal inline fun Encoder.encodeBuffered( + data: ByteArray, + buf: CharArray?, + maxBufSize: Int, + throwOnOverflow: Boolean, + _action: (buf: CharArray, offset: Int, len: Int) -> Unit, +): Long { + contract { callsInPlace(_action, InvocationKind.UNKNOWN) } + + if (buf != null) { + // Ensure function caller passed in buf.size for maxBufSize + check(buf.size == maxBufSize) { "buf.size[${buf.size}] != maxBufSize" } + } + if (config.maxEncodeEmitWithLineBreak == -1) { + // EncoderDecoder.Config implementation has not updated to + // new constructor which requires it to be greater than 0. + throw EncodingException("Encoder misconfiguration. ${this}.config.maxEncodeEmitWithLineBreak == -1") + } + require(maxBufSize > config.maxEncodeEmitWithLineBreak) { + val parameter = if (buf != null) "buf.size" else "maxBufSize" + "$parameter[$maxBufSize] <= ${this}.config.maxEncodeEmitWithLineBreak[${config.maxEncodeEmitWithLineBreak}]" + } + if (data.isEmpty()) return 0L + + try { + config.encodeOutMaxSize(data.size) + } catch (e: EncodingSizeException) { + if (throwOnOverflow) throw e + -1 + }.let { maxEncodeSize -> + if (maxEncodeSize !in 0..maxBufSize) return@let // Chunk + + // 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 }) + try { + _action(encoded, 0, i) + } finally { + if (config.backFillBuffers) encoded.fill('\u0000', 0, i) + } + return i.toLong() + } + + // Chunk + val _buf = buf ?: CharArray(maxBufSize) + val limit = _buf.size - config.maxEncodeEmitWithLineBreak + var iBuf = 0 + var size = 0L + try { + newEncoderFeed(out = { c -> _buf[iBuf++] = c }).use { feed -> + for (b in data) { + feed.consume(b) + if (iBuf <= limit) continue + _action(_buf, 0, iBuf) + size += iBuf + iBuf = 0 + } + } + if (iBuf == 0) return size + _action(_buf, 0, iBuf) + size += iBuf + } finally { + if (config.backFillBuffers) _buf.fill('\u0000') + } + return size } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt index 4f11936..a60ecaf 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/DecodeBufferedUnitTest.kt @@ -30,7 +30,7 @@ class DecodeBufferedUnitTest { fun givenBufferSize_whenLessThanOrEqualToConfigMaxDecodeEmit_thenThrowsIllegalArgumentException() { val decoder = TestEncoderDecoder(TestConfig( maxDecodeEmit = 255, - decodeInputReturn = { error("Should not make it here") } + decodeOutInputReturn = { error("Should not make it here") }, )) intArrayOf(254, 255).forEach { testSize -> @@ -58,7 +58,7 @@ class DecodeBufferedUnitTest { fun givenDecodeInputNonSizeException_whenConfigThrows_thenIsNeverIgnored() { val expected = "Config implementation has some sort of checksum at end of encoding and it did not pass" val decoder = TestEncoderDecoder(TestConfig( - decodeInputReturn = { throw EncodingException(expected) } + decodeOutInputReturn = { throw EncodingException(expected) }, )) try { "a".decodeBuffered(decoder, throwOnOverflow = true) { _, _, _ -> error("Should not make it here") } @@ -81,7 +81,7 @@ class DecodeBufferedUnitTest { "a".decodeBuffered( decoder, throwOnOverflow = true, - action = { _, _, _ -> error("Should not make it here") } + action = { _, _, _ -> error("Should not make it here") }, ) } } @@ -90,7 +90,7 @@ class DecodeBufferedUnitTest { fun givenDecodeInputSizeException_whenThrowOnOverflowFalse_thenEncodingSizeExceptionIsIgnored() { var invocationInput = 0 val decoder = TestEncoderDecoder(TestConfig( - decodeInputReturn = { invocationInput++; -1 }, + decodeOutInputReturn = { invocationInput++; -1 }, )) var invocationAction = 0 @@ -104,7 +104,7 @@ class DecodeBufferedUnitTest { action = { buf, _, _ -> invocationAction++ assertEquals(DEFAULT_BUFFER_SIZE, buf.size) - } + }, ) assertEquals(1, invocationInput) // Confirms that it went into stream decoding b/c was flushed 2 times @@ -118,7 +118,7 @@ class DecodeBufferedUnitTest { var invocationConsume = 0 val decoder = TestEncoderDecoder( config = TestConfig( - decodeInputReturn = { expectedSize }, + decodeOutInputReturn = { expectedSize }, ), decoderConsume = { invocationConsume++ }, decoderDoFinal = { (it as TestEncoderDecoder.DecoderFeed).getOut().output(1) }, @@ -137,7 +137,7 @@ class DecodeBufferedUnitTest { invocationAction++ assertEquals(expectedSize, buf.size) assertEquals(1, len) - } + }, ) assertEquals(1, invocationAction) assertEquals(invocationConsume, expectedInputSize) @@ -171,7 +171,7 @@ class DecodeBufferedUnitTest { invocationActionAssertion++ assertEquals(decoder.config.maxDecodeEmit, len) } - } + }, ) assertEquals(2, invocationAction) assertEquals(2, invocationActionAssertion) diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncodeBufferedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncodeBufferedUnitTest.kt new file mode 100644 index 0000000..bab1b52 --- /dev/null +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncodeBufferedUnitTest.kt @@ -0,0 +1,150 @@ +/* + * 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.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 +import io.matthewnelson.encoding.core.helpers.TestEncoderDecoder +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class EncodeBufferedUnitTest { + + @Test + fun givenBufferSize_whenLessThanOrEqualToConfigMaxEncodeEmitWithLineBreak_thenThrowsIllegalArgumentException() { + val encoder = TestEncoderDecoder(TestConfig( + maxEncodeEmit = 255, + decodeOutInputReturn = { error("Should not make it here") }, + )) + + intArrayOf(254, 255).forEach { testSize -> + assertFailsWith { + ByteArray(1).encodeBuffered( + encoder, + throwOnOverflow = true, + maxBufSize = testSize, + action = { _, _, _ -> error("Should not make it here") }, + ) + } + + assertFailsWith { + ByteArray(1).encodeBuffered( + encoder, + throwOnOverflow = true, + buf = CharArray(testSize), + action = { _, _, _ -> error("Should not make it here") }, + ) + } + } + } + + @Test + fun givenEncodeOutMaxSizeException_whenThrowOnOverflowTrue_thenEncodingSizeExceptionIsRethrown() { + val encoder = TestEncoderDecoder(TestConfig()) + assertFailsWith { + ByteArray(1).encodeBuffered( + encoder, + throwOnOverflow = true, + action = { _, _, _ -> error("Should not make it here") }, + ) + } + } + + @Test + fun givenEncodeOutMaxSizeException_whenThrowOnOverflowFalse_thenEncodingSizeExceptionIsIgnored() { + var invocationEncodeOut = 0 + val encoder = TestEncoderDecoder(TestConfig( + encodeOutReturn = { invocationEncodeOut++; -1 }, + )) + + var invocationAction = 0 + ByteArray(DEFAULT_BUFFER_SIZE + 50).encodeBuffered( + encoder, + throwOnOverflow = false, + action = { buf, _, _ -> + invocationAction++ + assertEquals(DEFAULT_BUFFER_SIZE, buf.size) + }, + ) + assertEquals(1, invocationEncodeOut) + // Confirms that it went into stream decoding b/c was flushed 2 times + assertEquals(2, invocationAction) + } + + @Test + fun givenInputSize_whenLessThanBufferSize_thenOneShotEncodesWithSmallerSize() { + val expectedSize = 2 + val expectedInputSize = DEFAULT_BUFFER_SIZE + 50 + var invocationConsume = 0 + val encoder = TestEncoderDecoder( + config = TestConfig( + encodeOutReturn = { expectedSize.toLong() }, + ), + encoderConsume = { invocationConsume++ }, + encoderDoFinal = { (it as TestEncoderDecoder.EncoderFeed).getOut().output(Char.MAX_VALUE) }, + ) + + var invocationAction = 0 + val result = ByteArray(expectedInputSize).encodeBuffered( + encoder, + throwOnOverflow = true, + maxBufSize = expectedSize * 10, + action = { buf, _, len -> + invocationAction++ + assertEquals(expectedSize, buf.size) + assertEquals(1, len) + }, + ) + assertEquals(1, invocationAction) + assertEquals(invocationConsume, expectedInputSize) + assertEquals(1L, result) + } + + @Test + fun givenConfigMaxEncodeEmitWithLineBreak_whenEncodingBuffered_thenFlushesIfLastIndexWouldBeExceeded() { + val encoder = TestEncoderDecoder(TestConfig(maxEncodeEmit = 50)) + + var invocationAction = 0 + var invocationActionAssertion = 0 + var actualLen = 0 + val result = ByteArray(DEFAULT_BUFFER_SIZE).encodeBuffered( + encoder, + throwOnOverflow = false, + maxBufSize = DEFAULT_BUFFER_SIZE, + action = { buf, _, len -> + invocationAction++ + actualLen += len + assertEquals(DEFAULT_BUFFER_SIZE, buf.size) + if (invocationAction == 1) { + invocationActionAssertion++ + assertEquals(DEFAULT_BUFFER_SIZE - encoder.config.maxEncodeEmitWithLineBreak + 1, len, "invocationAction[$invocationAction]") + } + if (invocationAction == 2) { + invocationActionAssertion++ + assertEquals(encoder.config.maxEncodeEmitWithLineBreak, len) + } + }, + ) + assertEquals(2, invocationAction) + assertEquals(2, invocationActionAssertion) + // TestEncoderDecoder.doFinal default value will output one 1 byte + assertEquals((DEFAULT_BUFFER_SIZE + 1).toLong(), result) + assertEquals(result, actualLen.toLong()) + } +} diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt index 6a4941e..e83ae0c 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderConfigUnitTest.kt @@ -20,92 +20,112 @@ import io.matthewnelson.encoding.core.util.DecoderInput import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertTrue import kotlin.test.fail class EncoderDecoderConfigUnitTest { + @Test + fun givenConfig_whenUsingDeprecatedConstructor_thenVersion260ParametersAreNegative1() { + @Suppress("DEPRECATION") + val config = object : EncoderDecoder.Config(isLenient = null, lineBreakInterval = 0, paddingChar = null) { + override fun encodeOutSizeProtected(unEncodedSize: Long): Long = error("") + override fun decodeOutMaxSizeProtected(encodedSize: Long): Long = error("") + override fun decodeOutMaxSizeOrFailProtected(encodedSize: Int, input: DecoderInput): Int = error("") + override fun toStringAddSettings(): Set = error("") + } + assertEquals(-1, config.maxDecodeEmit) + assertEquals(-1, config.maxEncodeEmit) + assertEquals(-1, config.maxEncodeEmitWithLineBreak) + } + @Test fun givenConfig_whenNegativeValuesSent_thenThrowsEncodingSizeExceptionBeforePassingToProtected() { val config = TestConfig( - encodeReturn = { - error("Should not make it here") - }, - decodeInputReturn = { - error("Should not make it here") - }, - decodeReturn = { - error("Should not make it here") - } + encodeOutReturn = { error("Should not make it here") }, + decodeOutInputReturn = { error("Should not make it here") }, + decodeOutReturn = { error("Should not make it here") }, ) assertFailsWith { config.decodeOutMaxSize(-1L) } assertFailsWith { config.encodeOutMaxSize(-1L) } + assertFailsWith { config.encodeOutMaxSize(-1) } } @Test fun givenConfig_whenNegativeValuesReturned_thenThrowsEncodingSizeException() { val config = TestConfig( - encodeReturn = { -1L }, - decodeInputReturn = { -1 }, - decodeReturn = { -1L }, + encodeOutReturn = { -1L }, + decodeOutInputReturn = { -1 }, + decodeOutReturn = { -1L }, maxDecodeEmit = 255, maxEncodeEmit = 255, ) assertFailsWith { config.decodeOutMaxSize(5) } assertFailsWith { config.decodeOutMaxSizeOrFail(DecoderInput("a")) } + assertFailsWith { config.encodeOutMaxSize(5L) } assertFailsWith { config.encodeOutMaxSize(5) } - assertFailsWith { TestConfig(maxDecodeEmit = 0) } - assertFailsWith { TestConfig(maxDecodeEmit = -1) } - assertFailsWith { TestConfig(maxDecodeEmit = 256) } - assertFailsWith { TestConfig(maxEncodeEmit = 0) } - assertFailsWith { TestConfig(maxEncodeEmit = -1) } - assertFailsWith { TestConfig(maxEncodeEmit = 256) } } @Test fun givenConfig_whenPositiveValuesSentAndReturned_thenDoseNotThrowException() { val config = TestConfig( - encodeReturn = { 1L }, - decodeInputReturn = { 1 }, - decodeReturn = { 1L }, + encodeOutReturn = { 1L }, + decodeOutInputReturn = { 1 }, + decodeOutReturn = { 1L }, ) config.decodeOutMaxSizeOrFail(DecoderInput("a")) config.decodeOutMaxSize(1L) config.encodeOutMaxSize(1L) + config.encodeOutMaxSize(1) } @Test fun givenConfig_whenZeroPassed_thenReturns0Immediately() { val config = TestConfig( - encodeReturn = { fail("Should not make it here") }, - decodeInputReturn = { fail("Should not make it here") }, - decodeReturn = { fail("Should not make it here") }, + encodeOutReturn = { fail("Should not make it here") }, + decodeOutInputReturn = { fail("Should not make it here") }, + decodeOutReturn = { fail("Should not make it here") }, ) - config.decodeOutMaxSizeOrFail(DecoderInput("")) - config.decodeOutMaxSize(0L) - config.encodeOutMaxSize(0L) + assertEquals(0, config.decodeOutMaxSizeOrFail(DecoderInput(""))) + assertEquals(0L, config.decodeOutMaxSize(0L)) + assertEquals(0, config.encodeOutMaxSize(0)) + assertEquals(0L, config.encodeOutMaxSize(0L)) } @Test fun givenConfig_whenZeroReturned_thenDoesNotThrowException() { val config = TestConfig( - encodeReturn = { 0L }, - decodeInputReturn = { 0 }, - decodeReturn = { 0L }, + encodeOutReturn = { 0L }, + decodeOutInputReturn = { 0 }, + decodeOutReturn = { 0L }, ) - config.decodeOutMaxSizeOrFail(DecoderInput("a")) - config.decodeOutMaxSize(1L) - config.encodeOutMaxSize(1L) + assertEquals(0, config.decodeOutMaxSizeOrFail(DecoderInput("a"))) + assertEquals(0L, config.decodeOutMaxSize(1L)) + assertEquals(0, config.encodeOutMaxSize(1)) + assertEquals(0L, config.encodeOutMaxSize(1L)) + } + + @Test + fun givenConfig_whenEncodeOutMaxSizeLongReturnsGreaterThanIntMax_thenEncodeOutMaxSizeIntThrowsEncodingSizeException() { + val config = TestConfig( + encodeOutReturn = { Int.MAX_VALUE.toLong() }, + ) + try { + config.encodeOutMaxSize(1, lineBreakInterval = 64) + fail() + } catch (e: EncodingSizeException) { + assertTrue(e.message.contains("maximum output Size[${Int.MAX_VALUE}]")) + } } @Test fun givenLineBreakInterval_whenIsLenientFalse_thenIsZero() { val config = TestConfig(isLenient = false, lineBreakInterval = 20) - assertEquals(0, config.lineBreakInterval) } @@ -113,7 +133,6 @@ class EncoderDecoderConfigUnitTest { fun givenLineBreakInterval_whenIsLenientTrue_thenIsExpected() { val expected: Byte = 20 val config = TestConfig(isLenient = true, lineBreakInterval = expected) - assertEquals(expected, config.lineBreakInterval) } @@ -121,40 +140,41 @@ class EncoderDecoderConfigUnitTest { fun givenLineBreakInterval_whenIsLenientNull_thenIsExpected() { val expected: Byte = 20 val config = TestConfig(isLenient = null, lineBreakInterval = expected) - assertEquals(expected, config.lineBreakInterval) } @Test fun givenConfig_whenLineBreakIntervalNegative_thenIsZero() { val config = TestConfig(isLenient = true, lineBreakInterval = -5) - assertEquals(0, config.lineBreakInterval) } @Test - fun givenConfig_whenLineBreakIntervalExpressed_thenIncreasesEncodeOutSize() { - val config = TestConfig(isLenient = true, lineBreakInterval = 10, encodeReturn = { it }) + fun givenConfig_whenLineBreakIntervalExpressed_thenCalculatesInflatedSize() { + val config = TestConfig(isLenient = true, lineBreakInterval = 10, encodeOutReturn = { it }) listOf( - Pair(5L, 5L), - Pair(10L, 10L), - Pair(21L, 20L), - Pair(32L, 30L), - Pair(43L, 40L), - ).forEach { (expected, actual) -> + Pair(0L, 5L), + Pair(0L, 10L), + Pair(1L, 20L), + Pair(2L, 30L), + Pair(3L, 40L), + Pair(399_999_999L, 4_000_000_000L), + Pair(400_000_000L, 4_000_000_019L), + ).forEach { (expectedInflation, actual) -> + val expected = actual + expectedInflation assertEquals(expected, config.encodeOutMaxSize(actual)) } } @Test fun givenLineBreakInterval_whenSizeIncreaseWouldExceedMaxValue_thenThrowsEncodingSizeException() { - val config = TestConfig(isLenient = true, lineBreakInterval = 10, encodeReturn = { it }) + val config = TestConfig(isLenient = true, lineBreakInterval = 10, encodeOutReturn = { it }) assertFailsWith { config.encodeOutMaxSize(Long.MAX_VALUE - 10L) } } @Test - fun givenConfig_whenLineBreakIntervalZero_thenDoesNotAffectEncodeOutSize() { - val config = TestConfig(encodeReturn = { it }) + fun givenConfig_whenLineBreakIntervalZero_thenDoesNotInflateEncodeOutSize() { + val config = TestConfig(encodeOutReturn = { it }) assertEquals(0, config.lineBreakInterval) listOf( @@ -168,6 +188,16 @@ class EncoderDecoderConfigUnitTest { } } + @Test + fun givenConfig_whenInvalidMaxEmitArguments_thenThrowsIllegalArgumentException() { + assertFailsWith { TestConfig(maxDecodeEmit = 0) } + assertFailsWith { TestConfig(maxDecodeEmit = -1) } + assertFailsWith { TestConfig(maxDecodeEmit = 256) } + assertFailsWith { TestConfig(maxEncodeEmit = 0) } + assertFailsWith { TestConfig(maxEncodeEmit = -1) } + assertFailsWith { TestConfig(maxEncodeEmit = 256) } + } + @Test fun givenCalculateMaxEncodeEmit_whenInvalidEmitSize_thenThrowsException() { assertFailsWith { EncoderDecoder.Config.calculateMaxEncodeEmit(0, 2) } @@ -192,6 +222,7 @@ class EncoderDecoderConfigUnitTest { fun givenCalculateMaxEncodeEmit_whenIntervalIn1ToEmitSize_thenReturnsExpected() { var i = 0 arrayOf( + Triple(1, 0, 1), // minimum possible value Triple(1, 1, 2), Triple(2, 1, 4), Triple(3, 2, 5), @@ -202,6 +233,7 @@ class EncoderDecoderConfigUnitTest { Triple(10, 5, 12), Triple(189, 5, 227), Triple(189, 64, 192), + Triple(255, 1, 510), // maximum possible value ).forEach { (size, interval, expected) -> val actual = EncoderDecoder.Config.calculateMaxEncodeEmit(size, interval) assertEquals(expected, actual, "arguments at index[$i]") diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt index 1a09072..977e75b 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/EncoderDecoderFeedUnitTest.kt @@ -267,7 +267,7 @@ class EncoderDecoderFeedUnitTest { config = TestConfig( isLenient = true, lineBreakInterval = 2, - encodeReturn = { it } + encodeOutReturn = { it } ) ) @@ -301,7 +301,7 @@ class EncoderDecoderFeedUnitTest { config = TestConfig( isLenient = true, lineBreakInterval = 2, - encodeReturn = { it } + encodeOutReturn = { it } ) ) diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt index 3081a79..c75c228 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/helpers/TestConfig.kt @@ -27,9 +27,9 @@ class TestConfig public constructor( paddingChar: Char? = '=', maxDecodeEmit: Int = 1, maxEncodeEmit: Int = 1, - private val encodeReturn: (unEncodedSize: Long) -> Long = { -1L }, - private val decodeInputReturn: (encodedSize: Int) -> Int = { -1 }, - private val decodeReturn: (encodedSize: Long) -> Long = { -1L }, + private val encodeOutReturn: (unEncodedSize: Long) -> Long = { -1L }, + private val decodeOutInputReturn: (encodedSize: Int) -> Int = { -1 }, + private val decodeOutReturn: (encodedSize: Long) -> Long = { -1L }, ): EncoderDecoder.Config( isLenient, lineBreakInterval, @@ -40,13 +40,13 @@ class TestConfig public constructor( backFillBuffers = true, ) { override fun decodeOutMaxSizeProtected(encodedSize: Long): Long { - return decodeReturn.invoke(encodedSize) + return decodeOutReturn.invoke(encodedSize) } override fun decodeOutMaxSizeOrFailProtected(encodedSize: Int, input: DecoderInput): Int { - return decodeInputReturn.invoke(encodedSize) + return decodeOutInputReturn.invoke(encodedSize) } override fun encodeOutSizeProtected(unEncodedSize: Long): Long { - return encodeReturn.invoke(unEncodedSize) + return encodeOutReturn.invoke(unEncodedSize) } override fun toStringAddSettings(): Set = emptySet() } diff --git a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt index 09b2c09..58bbe1b 100644 --- a/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt +++ b/library/core/src/commonTest/kotlin/io/matthewnelson/encoding/core/util/DecoderInputUnitTest.kt @@ -33,7 +33,7 @@ class DecoderInputUnitTest { val config = TestConfig( paddingChar = pad, - decodeInputReturn = { encodedSize -> + decodeOutInputReturn = { encodedSize -> assertEquals(expectedEncodedSize, encodedSize) expectedOutSize // return } @@ -50,7 +50,7 @@ class DecoderInputUnitTest { val config = TestConfig( isLenient = false, - decodeInputReturn = { validSize -> + decodeOutInputReturn = { validSize -> assertEquals(valid.length, validSize) 1 // return some positive value } @@ -83,7 +83,7 @@ class DecoderInputUnitTest { val expected = 1 val config = TestConfig( isLenient = true, - decodeInputReturn = { expected } + decodeOutInputReturn = { expected } ) listOf( @@ -102,7 +102,7 @@ class DecoderInputUnitTest { // Include spaces and set isLenient = true (so they are // skipped) in order to exercise DecoderInput.get val validInput = "D " as CharSequence - val config = TestConfig(isLenient = true, decodeInputReturn = { inputSize -> + val config = TestConfig(isLenient = true, decodeOutInputReturn = { inputSize -> assertEquals(1, inputSize) inputSize// pass }) @@ -115,7 +115,7 @@ class DecoderInputUnitTest { // Include spaces and set isLenient = true (so they are // skipped) in order to exercise DecoderInput.get val validInput = CharArray(5) { ' ' }.apply { set(0, 'D') } - val config = TestConfig(isLenient = true, decodeInputReturn = { inputSize -> + val config = TestConfig(isLenient = true, decodeOutInputReturn = { inputSize -> assertEquals(1, inputSize) inputSize// pass }) @@ -128,7 +128,7 @@ class DecoderInputUnitTest { // Include spaces and set isLenient = true (so they are // skipped) in order to exercise DecoderInput.get val validInput = ByteArray(5) { ' '.code.toByte() }.apply { set(0, 'D'.code.toByte()) } - val config = TestConfig(isLenient = true, decodeInputReturn = { inputSize -> + val config = TestConfig(isLenient = true, decodeOutInputReturn = { inputSize -> assertEquals(1, inputSize) inputSize// pass })