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
5 changes: 5 additions & 0 deletions library/core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,8 @@ public final class io/matthewnelson/encoding/core/util/LineBreakOutFeed : io/mat
public final fun reset ()V
}

public final class io/matthewnelson/encoding/core/util/TextUtil {
public static final fun wipe (Ljava/lang/StringBuilder;)Ljava/lang/StringBuilder;
public static final fun wipe (Ljava/lang/StringBuilder;I)Ljava/lang/StringBuilder;
}

2 changes: 2 additions & 0 deletions library/core/api/core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,6 @@ sealed class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.matth
}

final fun (io.matthewnelson.encoding.core/EncoderDecoder.Feed<*>?).io.matthewnelson.encoding.core/useFinally(kotlin/Throwable?) // io.matthewnelson.encoding.core/useFinally|[email protected]<*>?(kotlin.Throwable?){}[0]
final fun (kotlin.text/StringBuilder).io.matthewnelson.encoding.core.util/wipe(kotlin/Int): kotlin.text/StringBuilder // io.matthewnelson.encoding.core.util/wipe|[email protected](kotlin.Int){}[0]
final inline fun (kotlin.text/StringBuilder).io.matthewnelson.encoding.core.util/wipe(): kotlin.text/StringBuilder // io.matthewnelson.encoding.core.util/wipe|[email protected](){}[0]
final inline fun <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config, #B: io.matthewnelson.encoding.core/EncoderDecoder.Feed<#A>?, #C: kotlin/Any?> (#B).io.matthewnelson.encoding.core/use(kotlin/Function1<#B, #C>): #C // io.matthewnelson.encoding.core/use|use@0:1(kotlin.Function1<0:1,0:2>){0§<io.matthewnelson.encoding.core.EncoderDecoder.Config>;1§<io.matthewnelson.encoding.core.EncoderDecoder.Feed<0:0>?>;2§<kotlin.Any?>}[0]
16 changes: 15 additions & 1 deletion library/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,19 @@ plugins {
}

kmpConfiguration {
configureShared(java9ModuleName = "io.matthewnelson.encoding.core", publish = true) {}
configureShared(java9ModuleName = "io.matthewnelson.encoding.core", publish = true) {
kotlin {
with(sourceSets) {
val sets = arrayOf(
"jvm",
"native",
"wasmJs",
"wasmWasi",
).mapNotNull { name -> findByName(name + "Test") }
if (sets.isEmpty()) return@kotlin
val test = maybeCreate("nonJsTest").apply { dependsOn(getByName("commonTest")) }
sets.forEach { t -> t.dependsOn(test) }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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.util.LineBreakOutFeed
import io.matthewnelson.encoding.core.util.wipe
import kotlin.jvm.JvmField
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
Expand Down Expand Up @@ -221,14 +222,8 @@ public sealed class Encoder<C: EncoderDecoder.Config>(config: C): Decoder<C>(con
return encoder.encodeOutMaxSizeOrFail(size) { maxSize ->
val sb = StringBuilder(maxSize)
encoder.encode(this, sb::append)
val length = sb.length
val result = sb.toString()
if (encoder.config.backFillBuffers) {
// Some implementations of StringBuilder do not overwrite buffered
// data when clear() is used. Must set to 0 length and do manually.
sb.setLength(0)
repeat(length) { sb.append(' ') }
}
if (encoder.config.backFillBuffers) sb.wipe()
result
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
@file:Suppress("KotlinRedundantDiagnosticSuppress")
@file:Suppress("NOTHING_TO_INLINE")

package io.matthewnelson.encoding.core.internal

@Suppress("NOTHING_TO_INLINE")
internal inline fun Char.isSpaceOrNewLine(): Boolean {
return when(this) {
'\n', '\r', ' ', '\t' -> true
else -> false
}
}

// Here for testing purposes. Implementation uses finalLen = 0
internal inline fun StringBuilder.commonWipe(len: Int, finalLen: Int): StringBuilder {
setLength(0)
// Kotlin/Js returns StringBuilder.length for capacity() as there is
// no backing array. On all other platforms this will be the backing
// array size. So will always return here on Kotlin/Js b/c we just set
// length to 0.
@Suppress("DEPRECATION")
val cap = capacity()
if (cap == 0) return this
// All other platforms will set the new length from 0 to newLen, and
// in doing so will fill their backing arrays via Array.fill
val newLen = if (len !in 1..cap) cap else len
setLength(newLen)
setLength(finalLen)
return this
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.
**/
@file:JvmName("TextUtil")
@file:Suppress("NOTHING_TO_INLINE")

package io.matthewnelson.encoding.core.util

import io.matthewnelson.encoding.core.internal.commonWipe
import kotlin.jvm.JvmName

/**
* Wipes the [StringBuilder] backing array from index `0` to [StringBuilder.length]
* (exclusive) with the null character `\u0000`, and then sets it's length back to `0`.
* If [StringBuilder.length] is `0`, then [StringBuilder.capacity] will be used.
*
* On Kotlin/Js there is no backing array, so after setting length to `0` will return
* early.
*
* @return [StringBuilder]
* */
public inline fun StringBuilder.wipe(): StringBuilder = wipe(length)

/**
* Wipes the [StringBuilder] backing array from index `0` to [len] (exclusive)
* with the null character `\u0000`, and then sets it's length back to `0`.
* If [len] is less than `1` or greater than [StringBuilder.capacity], then
* [StringBuilder.capacity] is used in place of [len].
*
* On Kotlin/Js there is no backing array, so after setting length to `0` will return
* early.
*
* @param [len] The length (exclusive), starting from index `0`, to wipe. If less
* than `1` or greater than [StringBuilder.capacity], then [StringBuilder.capacity]
* is used instead.
*
* @return [StringBuilder]
* */
public fun StringBuilder.wipe(len: Int): StringBuilder = commonWipe(len, finalLen = 0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.util

import io.matthewnelson.encoding.core.internal.commonWipe
import kotlin.test.Test
import kotlin.test.assertEquals

@Suppress("DEPRECATION")
class TextUtilUnitTest {

@Test
fun givenStringBuilder_whenCapacity0_thenWipeDoesNothing() {
val sb = StringBuilder(0)
assertEquals(0, sb.capacity())
// If we didn't return early (capacity == 0),
// finalLen of 1 would extend capacity.
sb.commonWipe(sb.length, finalLen = 1)
assertEquals(0, sb.capacity())
assertEquals(0, sb.length)
}

@Test
fun givenStringBuilder_whenWipe_thenSetsFinalLength0() {
val sb = StringBuilder(1).append('a')
assertEquals(1, sb.length)
sb.wipe()
assertEquals(0, sb.length)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.util

import io.matthewnelson.encoding.core.internal.commonWipe
import kotlin.test.Test
import kotlin.test.assertEquals

class TextUtilNonJsUnitTest {

@Test
fun givenStringBuilder_whenWipeLenLessThan1_thenUsesCapacity() {
val expected = 4
val sb = StringBuilder(expected)
repeat(expected) { sb.append('a') }
assertEquals(expected, sb.capacity())
assertEquals(expected, sb.length)

// 0
sb.commonWipe(len = 0, finalLen = 1)
assertEquals(expected, sb.capacity())
// Confirm via finalLen that it did not return early
assertEquals(1, sb.length)

repeat(expected - 1) { sb.append('a') }
assertEquals(expected, sb.capacity())
assertEquals(expected, sb.length)

// Try negative
sb.commonWipe(len = -1, finalLen = 1)
assertEquals(expected, sb.capacity())
// Confirm via finalLen that it did not return early
assertEquals(1, sb.length)
}

@Test
fun givenStringBuilder_whenWipeLenGreaterThanCapacity_thenUsesCapacity() {
val expected = 4
val sb = StringBuilder(expected)
repeat(expected) { sb.append('a') }
assertEquals(expected, sb.capacity())
assertEquals(expected, sb.length)

// 0
sb.commonWipe(len = expected + 1, finalLen = 1)
// backing array did not increase
assertEquals(expected, sb.capacity())
// Confirm via finalLen that it did not return early
assertEquals(1, sb.length)
}
}