Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ public class Base16: EncoderDecoder<Base16.Config> {
lineBreakResetOnFlush,
paddingChar = null,
maxDecodeEmit = 1,
maxEncodeEmit = 2,
backFillBuffers,
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
lineBreakResetOnFlush = false,
paddingChar = null,
maxDecodeEmit = 5,
maxEncodeEmit = calculateMaxEncodeEmit(emitSize = 8, insertionInterval = hyphenInterval.toInt()),
backFillBuffers,
) {

Expand Down Expand Up @@ -642,6 +643,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
lineBreakResetOnFlush,
paddingChar = '=',
maxDecodeEmit = 5,
maxEncodeEmit = 8,
backFillBuffers,
) {

Expand Down Expand Up @@ -983,6 +985,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
lineBreakResetOnFlush,
paddingChar = '=',
maxDecodeEmit = 5,
maxEncodeEmit = 8,
backFillBuffers,
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ internal inline fun ((Boolean, Boolean, Byte, Char?, Boolean, Boolean) -> Base32
b: Base32.Crockford.Builder,
noinline crockford: (Base32.Crockford.Config, Any?) -> Base32.Crockford,
): Base32.Crockford {
val hyphenInterval = if (b._hyphenInterval <= 0) 0 else b._hyphenInterval
if (
b._isLenient == Base32.Crockford.DELEGATE.config.isLenient
&& b._encodeLowercase == Base32.Crockford.DELEGATE.config.encodeLowercase
&& b._hyphenInterval == Base32.Crockford.DELEGATE.config.hyphenInterval
&& hyphenInterval == Base32.Crockford.DELEGATE.config.hyphenInterval
&& b._checkSymbol == Base32.Crockford.DELEGATE.config.checkSymbol
&& b._finalizeWhenFlushed == Base32.Crockford.DELEGATE.config.finalizeWhenFlushed
&& b._backFillBuffers == Base32.Crockford.DELEGATE.config.backFillBuffers
Expand All @@ -38,7 +39,7 @@ internal inline fun ((Boolean, Boolean, Byte, Char?, Boolean, Boolean) -> Base32
val config = this(
b._isLenient,
b._encodeLowercase,
b._hyphenInterval,
hyphenInterval,
b._checkSymbol,
b._finalizeWhenFlushed,
b._backFillBuffers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ public class Base64: EncoderDecoder<Base64.Config> {
lineBreakResetOnFlush,
paddingChar = '=',
maxDecodeEmit = 3,
maxEncodeEmit = 4,
backFillBuffers,
) {

Expand Down
5 changes: 4 additions & 1 deletion library/core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ public abstract class io/matthewnelson/encoding/core/EncoderDecoder$Config {
public final field lineBreakInterval B
public final field lineBreakResetOnFlush Z
public final field maxDecodeEmit I
public final field maxEncodeEmit I
public final field paddingChar Ljava/lang/Character;
public fun <init> (Ljava/lang/Boolean;BLjava/lang/Character;)V
protected fun <init> (Ljava/lang/Boolean;BZLjava/lang/Character;IZ)V
protected fun <init> (Ljava/lang/Boolean;BZLjava/lang/Character;IIZ)V
public static final fun calculateMaxEncodeEmit (II)I
public final fun decodeOutMaxSize (J)J
public final fun decodeOutMaxSizeOrFail (Lio/matthewnelson/encoding/core/util/DecoderInput;)I
protected abstract fun decodeOutMaxSizeOrFailProtected (ILio/matthewnelson/encoding/core/util/DecoderInput;)I
Expand All @@ -144,6 +146,7 @@ public abstract class io/matthewnelson/encoding/core/EncoderDecoder$Config {
}

public final class io/matthewnelson/encoding/core/EncoderDecoder$Config$Companion {
public final fun calculateMaxEncodeEmit (II)I
public final fun outSizeExceedsMaxEncodingSizeException (Ljava/lang/Number;Ljava/lang/Number;)Lio/matthewnelson/encoding/core/EncodingSizeException;
}

Expand Down
5 changes: 4 additions & 1 deletion library/core/api/core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat
final fun toString(): kotlin/String // io.matthewnelson.encoding.core/EncoderDecoder.toString|toString(){}[0]

abstract class Config { // io.matthewnelson.encoding.core/EncoderDecoder.Config|null[0]
constructor <init>(kotlin/Boolean?, kotlin/Byte, kotlin/Boolean, kotlin/Char?, kotlin/Int, kotlin/Boolean) // io.matthewnelson.encoding.core/EncoderDecoder.Config.<init>|<init>(kotlin.Boolean?;kotlin.Byte;kotlin.Boolean;kotlin.Char?;kotlin.Int;kotlin.Boolean){}[0]
constructor <init>(kotlin/Boolean?, kotlin/Byte, kotlin/Boolean, kotlin/Char?, kotlin/Int, kotlin/Int, kotlin/Boolean) // io.matthewnelson.encoding.core/EncoderDecoder.Config.<init>|<init>(kotlin.Boolean?;kotlin.Byte;kotlin.Boolean;kotlin.Char?;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0]
constructor <init>(kotlin/Boolean?, kotlin/Byte, kotlin/Char?) // io.matthewnelson.encoding.core/EncoderDecoder.Config.<init>|<init>(kotlin.Boolean?;kotlin.Byte;kotlin.Char?){}[0]

final val backFillBuffers // io.matthewnelson.encoding.core/EncoderDecoder.Config.backFillBuffers|{}backFillBuffers[0]
Expand All @@ -49,6 +49,8 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat
final fun <get-lineBreakResetOnFlush>(): kotlin/Boolean // io.matthewnelson.encoding.core/EncoderDecoder.Config.lineBreakResetOnFlush.<get-lineBreakResetOnFlush>|<get-lineBreakResetOnFlush>(){}[0]
final val maxDecodeEmit // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxDecodeEmit|{}maxDecodeEmit[0]
final fun <get-maxDecodeEmit>(): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxDecodeEmit.<get-maxDecodeEmit>|<get-maxDecodeEmit>(){}[0]
final val maxEncodeEmit // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmit|{}maxEncodeEmit[0]
final fun <get-maxEncodeEmit>(): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.maxEncodeEmit.<get-maxEncodeEmit>|<get-maxEncodeEmit>(){}[0]
final val paddingChar // io.matthewnelson.encoding.core/EncoderDecoder.Config.paddingChar|{}paddingChar[0]
final fun <get-paddingChar>(): kotlin/Char? // io.matthewnelson.encoding.core/EncoderDecoder.Config.paddingChar.<get-paddingChar>|<get-paddingChar>(){}[0]

Expand Down Expand Up @@ -80,6 +82,7 @@ abstract class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.mat
}

final object Companion { // io.matthewnelson.encoding.core/EncoderDecoder.Config.Companion|null[0]
final fun calculateMaxEncodeEmit(kotlin/Int, kotlin/Int): kotlin/Int // io.matthewnelson.encoding.core/EncoderDecoder.Config.Companion.calculateMaxEncodeEmit|calculateMaxEncodeEmit(kotlin.Int;kotlin.Int){}[0]
final fun outSizeExceedsMaxEncodingSizeException(kotlin/Number, kotlin/Number): io.matthewnelson.encoding.core/EncodingSizeException // io.matthewnelson.encoding.core/EncoderDecoder.Config.Companion.outSizeExceedsMaxEncodingSizeException|outSizeExceedsMaxEncodingSizeException(kotlin.Number;kotlin.Number){}[0]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import io.matthewnelson.encoding.core.internal.closedException
import io.matthewnelson.encoding.core.internal.isSpaceOrNewLine
import io.matthewnelson.encoding.core.util.DecoderInput
import io.matthewnelson.encoding.core.util.LineBreakOutFeed
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmField
import kotlin.jvm.JvmStatic

Expand Down Expand Up @@ -109,12 +112,39 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(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
* in version `2.6.0`, and as such is unable to be used with `:core` module APIs dependent
* on this value (such as [Decoder.decodeBuffered] or [Decoder.decodeBufferedAsync]).
* */
@JvmField
public val maxDecodeEmit: Int,

/**
* The maximum number of characters that the implementation's [Encoder.Feed] can
* potentially emit on a single invocation of [Encoder.Feed.consume], [Encoder.Feed.flush],
* or [Encoder.Feed.doFinal].
*
* 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
* to text transformations) can emit `4` characters for every `4` bytes of input (depending
* on the implementation), so its maximum emission would be `4`.
*
* **NOTE:** This value does **not** take into consideration the [lineBreakInterval] setting,
* or any other [LineBreakOutFeed]-like implementation that may inflate the maximum character
* emission size. Implementations must **only** consider their own [LineBreakOutFeed]-like
* implementation details (such as the hyphen interval for `Base32.Crockford`) when calculating
* their maximum character emission size.
*
* 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.
*
* @see [Companion.calculateMaxEncodeEmit]
* */
@JvmField
public val maxEncodeEmit: Int,

/**
* When the functions [Encoder.encodeToString], [Encoder.encodeToCharArray],
* [Decoder.decodeToByteArray], [Decoder.decodeBuffered], and [Decoder.decodeBufferedAsync]
Expand All @@ -139,26 +169,29 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
/**
* Instantiates a new [Config] instance.
*
* @throws [IllegalArgumentException] If [maxDecodeEmit] is less than `1` or greater than `255`.
* @throws [IllegalArgumentException] If [maxDecodeEmit] is less than `1` or greater than
* `255`. If [maxEncodeEmit] is less than `1` or greater than `255`.
* */
protected constructor(
isLenient: Boolean?,
lineBreakInterval: Byte,
lineBreakResetOnFlush: Boolean,
paddingChar: Char?,
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,
) {
require(maxDecodeEmit > 0) { "maxDecodeEmit must be greater than 0" }
require(maxDecodeEmit < 256) { "maxDecodeEmit must be less than 256" }
checkMaxEmitSize(maxDecodeEmit) { "maxDecodeEmit" }
checkMaxEmitSize(maxEncodeEmit) { "maxEncodeEmit" }
}

/**
Expand Down Expand Up @@ -339,6 +372,76 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
return outSize
}

public companion object {

/**
* Calculates and returns the maximum character emission size, given some sort of
* [emitSize] and desire to insert characters every [insertionInterval]. The way
* things are calculated are based on how [lineBreakInterval] operates, whereby
* if [insertionInterval] encoded characters have been output, the next encoded
* character output will be preceded with some arbitrary character (such as a
* hyphen for `Base32.Crockford`, which uses this function to calculate its final
* [maxEncodeEmit] value passed to the [Config] constructor).
*
* **NOTE:** Implementors of [Config] utilizing this to calculate their [maxEncodeEmit]
* values must consider that it may return a value greater than `255`, depending on the
* input arguments, resulting in an [IllegalArgumentException] when passed into the
* [Config] constructor. For example, an [insertionInterval] of `1` will inflate the
* provided [emitSize] by 2x.
*
* @param [emitSize] The number of characters that are expected to be emitted.
* @param [insertionInterval] The interval at which `1` character is to be inserted.
*
* @return The calculated emission size for a provided character [insertionInterval], or
* [emitSize] itself if [insertionInterval] is less than `1`.
*
* @see [lineBreakInterval]
* @see [maxEncodeEmit]
*
* @throws [IllegalArgumentException] If [emitSize] is less than `1` or greater than `255`.
* */
@JvmStatic
public fun calculateMaxEncodeEmit(emitSize: Int, insertionInterval: Int): Int {
checkMaxEmitSize(emitSize) { "emitSize" }
if (insertionInterval <= 0) return emitSize
if (insertionInterval >= emitSize) return emitSize + 1

// Starting count at insertionInterval instead of 0 simulates the case of
// the very next output of emitSize encoded characters is to be preceded
// by the insertion character, such as a new line `\n`, whereby the max
// can be calculated.
//
// The limits for when this run an emitSize of 255 and an insertionInterval
// of emitSize - 1. Given how small actual emitSizes are expected to be,
// such as 8 for Base32, calculating things this way is OK with me...
var count = insertionInterval
var output = 0
var i = 0
while (i++ < emitSize) {
if (count == insertionInterval) {
output++
count = 0
}
output++
count++
}
return output
}

/**
* Helper for generating an [EncodingSizeException] when the
* pre-calculated encoded/decoded output size exceeds the maximum for
* the given encoding/decoding specification.
* */
@JvmStatic
public fun outSizeExceedsMaxEncodingSizeException(
inputSize: Number,
maxSize: Number,
): EncodingSizeException = EncodingSizeException(
"Size[$inputSize] of input would exceed the maximum output Size[$maxSize] for this operation."
)
}

/**
* Calculate and return an exact (preferably), or maximum, size that an encoding would be
* for the [unEncodedSize] data.
Expand Down Expand Up @@ -430,6 +533,7 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
if (other.lineBreakResetOnFlush != this.lineBreakResetOnFlush) return false
if (other.paddingChar != this.paddingChar) return false
if (other.maxDecodeEmit != this.maxDecodeEmit) return false
if (other.maxEncodeEmit != this.maxEncodeEmit) return false
if (other.backFillBuffers != this.backFillBuffers) return false
if (other::class != this::class) return false
return other._toStringAddSettings == this._toStringAddSettings
Expand All @@ -443,6 +547,7 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
result = result * 31 + lineBreakResetOnFlush.hashCode()
result = result * 31 + paddingChar.hashCode()
result = result * 31 + maxDecodeEmit.hashCode()
result = result * 31 + maxEncodeEmit.hashCode()
result = result * 31 + backFillBuffers.hashCode()
result = result * 31 + this::class.hashCode()
result = result * 31 + _toStringAddSettings.hashCode()
Expand All @@ -462,6 +567,8 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
appendLine(paddingChar)
append(" maxDecodeEmit: ")
appendLine(maxDecodeEmit)
append(" maxEncodeEmit: ")
appendLine(maxEncodeEmit)
append(" backFillBuffers: ")
append(backFillBuffers) // last one uses append, not appendLine

Expand All @@ -475,22 +582,6 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
append(']')
}.toString()

public companion object {

/**
* Helper for generating an [EncodingSizeException] when the
* pre-calculated encoded/decoded output size exceeds the maximum for
* the given encoding/decoding specification.
* */
@JvmStatic
public fun outSizeExceedsMaxEncodingSizeException(
inputSize: Number,
maxSize: Number,
): EncodingSizeException = EncodingSizeException(
"Size[$inputSize] of input would exceed the maximum output Size[$maxSize] for this operation."
)
}

/**
* DEPRECATED since `2.6.0`
* @see [encodeOutMaxSize]
Expand Down Expand Up @@ -520,9 +611,9 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
* @suppress
* */
@Deprecated(
message = "Parameters, lineBreakResetOnFlush, maxDecodeEmit, and backFillBuffers were added. Use the new constructor.",
message = "Parameters, lineBreakResetOnFlush, maxDecodeEmit, maxEncodeEmit, and backFillBuffers were added. Use the new constructor.",
replaceWith = ReplaceWith(
expression = "EncoderDecoder.Config(isLenient, lineBreakInterval, lineBreakResetOnFlush = false, paddingChar, maxDecodeEmit = 0 /* TODO */, backFillBuffers = true)"),
expression = "EncoderDecoder.Config(isLenient, lineBreakInterval, lineBreakResetOnFlush = false, paddingChar, maxDecodeEmit = 0 /* TODO */, maxEncodeEmit = 0 /* TODO */, backFillBuffers = true)"),
level = DeprecationLevel.WARNING,
)
public constructor(
Expand All @@ -535,6 +626,7 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
lineBreakResetOnFlush = false,
paddingChar = paddingChar,
maxDecodeEmit = -1, // NOTE: NEVER change.
maxEncodeEmit = -1, // NOTE: NEVER change.
backFillBuffers = true,
unused = null,
)
Expand Down Expand Up @@ -699,3 +791,17 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
private inline fun lineBreakIntervalOrZero(isLenient: Boolean?, interval: Byte): Byte {
return if (isLenient != false && interval > 0) interval else 0
}

@OptIn(ExperimentalContracts::class)
@Throws(IllegalArgumentException::class)
private inline fun checkMaxEmitSize(size: Int, parameterName: () -> String) {
contract { callsInPlace(parameterName, InvocationKind.AT_MOST_ONCE) }
if (size <= 0) {
val n = parameterName()
throw IllegalArgumentException("$n must be greater than 0")
}
if (size >= 256) {
val n = parameterName()
throw IllegalArgumentException("$n must be less than 256")
}
}
Loading