diff --git a/libraries/stdlib/js/src/kotlin/text/StringBuilderJs.kt b/libraries/stdlib/js/src/kotlin/text/StringBuilderJs.kt index fc12d7cabea..cfbce659039 100644 --- a/libraries/stdlib/js/src/kotlin/text/StringBuilderJs.kt +++ b/libraries/stdlib/js/src/kotlin/text/StringBuilderJs.kt @@ -1,18 +1,32 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package kotlin.text -public actual class StringBuilder(content: String = "") : Appendable, CharSequence { - actual constructor(capacity: Int) : this() {} +/** + * A mutable sequence of characters. + * + * String builder can be used to efficiently perform multiple string manipulation operations. + */ +public actual class StringBuilder actual constructor(content: String) : Appendable, CharSequence { + /** + * Constructs an empty string builder with the specified initial [capacity]. + * + * In Kotlin/JS implementation of StringBuilder the initial capacity has no effect on the further performance of operations. + */ + actual constructor(capacity: Int) : this() { + this.asDynamic()._capacity = capacity + } + /** Constructs a string builder that contains the same characters as the specified [content] char sequence. */ actual constructor(content: CharSequence) : this(content.toString()) {} + /** Constructs an empty string builder. */ actual constructor() : this("") - private var string: String = content + private var string: String = if (content !== undefined) content else "" actual override val length: Int get() = string.asDynamic().length @@ -32,23 +46,325 @@ public actual class StringBuilder(content: String = "") : Appendable, CharSequen return this } - actual override fun append(csq: CharSequence?, start: Int, end: Int): StringBuilder { - string += csq.toString().substring(start, end) - return this - } - - actual fun append(obj: Any?): StringBuilder { - string += obj.toString() - return this - } + @UseExperimental(ExperimentalStdlibApi::class) + actual override fun append(csq: CharSequence?, start: Int, end: Int): StringBuilder = this.appendRange(csq, start, end) + /** + * Reverses the contents of this string builder and returns this instance. + * + * Surrogate pairs included in this string builder are treated as single characters. + * Therefore, the order of the high-low surrogates is never reversed. + * + * Note that the reverse operation may produce new surrogate pairs that were unpaired low-surrogates and high-surrogates before the operation. + * For example, reversing `"\uDC00\uD800"` produces `"\uD800\uDC00"` which is a valid surrogate pair. + */ actual fun reverse(): StringBuilder { - string = string.asDynamic().split("").reverse().join("") + var reversed = "" + var index = string.length - 1 + while (index >= 0) { + val low = string[index--] + if (low.isLowSurrogate() && index >= 0) { + val high = string[index--] + if (high.isHighSurrogate()) { + reversed = reversed + high + low + } else { + reversed = reversed + low + high + } + } else { + reversed += low + } + } + string = reversed return this } /** - * Clears the content of this string builder making it empty. + * Appends the string representation of the specified object [value] to this string builder and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was appended to this string builder. + */ + actual fun append(value: Any?): StringBuilder { + string += value.toString() + return this + } + + /** + * Appends the string representation of the specified boolean [value] to this string builder and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was appended to this string builder. + */ + @SinceKotlin("1.3") +// @ExperimentalStdlibApi + actual fun append(value: Boolean): StringBuilder { + string += value + return this + } + + /** + * Appends characters in the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at the index 0. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun append(value: CharArray): StringBuilder { + string += value.concatToString() + return this + } + + /** + * Appends the specified string [value] to this string builder and returns this instance. + */ + @SinceKotlin("1.3") +// @ExperimentalStdlibApi + actual fun append(value: String): StringBuilder { + this.string += value + return this + } + + /** + * Returns the current capacity of this string builder. + * + * The capacity is the maximum length this string builder can have before an allocation occurs. + * + * In Kotlin/JS implementation of StringBuilder the value returned from this method may not indicate the actual size of the backing storage. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun capacity(): Int = if (this.asDynamic()._capacity !== undefined) maxOf(this.asDynamic()._capacity, length) else length + + /** + * Ensures that the capacity of this string builder is at least equal to the specified [minimumCapacity]. + * + * If the current capacity is less than the [minimumCapacity], a new backing storage is allocated with greater capacity. + * Otherwise, this method takes no action and simply returns. + * + * In Kotlin/JS implementation of StringBuilder the size of the backing storage is not extended to comply the given [minimumCapacity], + * thus calling this method has no effect on the further performance of operations. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun ensureCapacity(minimumCapacity: Int) { + if (minimumCapacity > capacity()) { + this.asDynamic()._capacity = minimumCapacity + } + } + + /** + * Returns the index within this string builder of the first occurrence of the specified [string]. + * + * Returns `-1` if the specified [string] does not occur in this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun indexOf(string: String): Int = this.string.asDynamic().indexOf(string) + + /** + * Returns the index within this string builder of the first occurrence of the specified [string], + * starting at the specified [startIndex]. + * + * Returns `-1` if the specified [string] does not occur in this string builder starting at the specified [startIndex]. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun indexOf(string: String, startIndex: Int): Int = this.string.asDynamic().indexOf(string, startIndex) + + /** + * Returns the index within this string builder of the last occurrence of the specified [string]. + * The last occurrence of empty string `""` is considered to be at the index equal to `this.length`. + * + * Returns `-1` if the specified [string] does not occur in this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun lastIndexOf(string: String): Int = this.string.asDynamic().lastIndexOf(string) + + /** + * Returns the index within this string builder of the last occurrence of the specified [string], + * starting from the specified [startIndex] toward the beginning. + * + * Returns `-1` if the specified [string] does not occur in this string builder starting at the specified [startIndex]. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun lastIndexOf(string: String, startIndex: Int): Int { + if (string.isEmpty() && startIndex < 0) return -1 + return this.string.asDynamic().lastIndexOf(string, startIndex) + } + + /** + * Inserts the string representation of the specified boolean [value] into this string builder at the specified [index] and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was inserted into this string builder at the specified [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun insert(index: Int, value: Boolean): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + string = string.substring(0, index) + value + string.substring(index) + return this + } + + /** + * Inserts the specified character [value] into this string builder at the specified [index] and returns this instance. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun insert(index: Int, value: Char): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + string = string.substring(0, index) + value + string.substring(index) + return this + } + + /** + * Inserts characters in the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] character array, starting at [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun insert(index: Int, value: CharArray): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + string = string.substring(0, index) + value.concatToString() + string.substring(index) + return this + } + + /** + * Inserts characters in the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which characters are inserted. If [value] is `null`, then the four characters `"null"` are inserted. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun insert(index: Int, value: CharSequence?): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + string = string.substring(0, index) + value.toString() + string.substring(index) + return this + } + + /** + * Inserts the string representation of the specified object [value] into this string builder at the specified [index] and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was inserted into this string builder at the specified [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun insert(index: Int, value: Any?): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + string = string.substring(0, index) + value.toString() + string.substring(index) + return this + } + + /** + * Inserts the string [value] into this string builder at the specified [index] and returns this instance. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun insert(index: Int, value: String): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + this.string = this.string.substring(0, index) + value + this.string.substring(index) + return this + } + + /** + * Sets the length of this string builder to the specified [newLength]. + * + * If the [newLength] is less than the current length, it is changed to the specified [newLength]. + * Otherwise, null characters '\u0000' are appended to this string builder until its length is less than the [newLength]. + * + * Note that in Kotlin/JS [set] operator function has non-constant execution time complexity. + * Therefore, increasing length of this string builder and then updating each character by index may slow down your program. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [newLength] is less than zero. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun setLength(newLength: Int) { + if (newLength < 0) { + throw IllegalArgumentException("Negative new length: $newLength.") + } + + if (newLength <= length) { + string = string.substring(0, newLength) + } else { + for (i in length until newLength) { + string += '\u0000' + } + } + } + + /** + * Returns a new [String] that contains characters in this string builder at [startIndex] (inclusive) and up to the [length] (exclusive). + * + * @throws IndexOutOfBoundsException if [startIndex] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun substring(startIndex: Int): String { + AbstractList.checkPositionIndex(startIndex, length) + + return string.substring(startIndex) + } + + /** + * Returns a new [String] that contains characters in this string builder at [startIndex] (inclusive) and up to the [endIndex] (exclusive). + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun substring(startIndex: Int, endIndex: Int): String { + AbstractList.checkBoundsIndexes(startIndex, endIndex, length) + + return string.substring(startIndex, endIndex) + } + + /** + * Attempts to reduce storage used for this string builder. + * + * If the backing storage of this string builder is larger than necessary to hold its current contents, + * then it may be resized to become more space efficient. + * Calling this method may, but is not required to, affect the value of the [capacity] property. + * + * In Kotlin/JS implementation of StringBuilder the size of the backing storage is always equal to the length of the string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + actual fun trimToSize() { + if (this.asDynamic()._capacity !== undefined) { + this.asDynamic()._capacity = length + } + } + + override fun toString(): String = string + + /** + * Clears the content of this string builder making it empty and returns this instance. * * @sample samples.text.Strings.clearStringBuilder */ @@ -58,16 +374,340 @@ public actual class StringBuilder(content: String = "") : Appendable, CharSequen return this } - override fun toString(): String = string + /** + * Sets the character at the specified [index] to the specified [value]. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public operator fun set(index: Int, value: Char) { + AbstractList.checkElementIndex(index, length) + + string = string.substring(0, index) + value + string.substring(index + 1) + } + + /** + * Replaces characters in the specified range of this string builder with characters in the specified string [value] and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to replace. + * @param endIndex the end (exclusive) of the range to replace. + * @param value the string to replace with. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [startIndex] is less than zero, greater than the length of this string builder, or `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun setRange(startIndex: Int, endIndex: Int, value: String): StringBuilder { + checkReplaceRange(startIndex, endIndex, length) + + this.string = this.string.substring(0, startIndex) + value + this.string.substring(endIndex) + return this + } + + private fun checkReplaceRange(startIndex: Int, endIndex: Int, length: Int) { + if (startIndex < 0 || startIndex > length) { + throw IndexOutOfBoundsException("startIndex: $startIndex, length: $length") + } + if (startIndex > endIndex) { + throw IllegalArgumentException("startIndex($startIndex) > endIndex($endIndex)") + } + } + + /** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun deleteAt(index: Int): StringBuilder { + AbstractList.checkElementIndex(index, length) + + string = string.substring(0, index) + string.substring(index + 1) + return this + } + + /** + * Removes characters in the specified range from this string builder and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to remove. + * @param endIndex the end (exclusive) of the range to remove. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun deleteRange(startIndex: Int, endIndex: Int): StringBuilder { + checkReplaceRange(startIndex, endIndex, length) + + string = string.substring(0, startIndex) + string.substring(endIndex) + return this + } + + /** + * Copies characters from this string builder into the [destination] character array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to, 0 by default. + * @param startIndex the beginning (inclusive) of the range to copy, 0 by default. + * @param endIndex the end (exclusive) of the range to copy, length of this string builder by default. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun toCharArray(destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = this.length) { + AbstractList.checkBoundsIndexes(startIndex, endIndex, length) + AbstractList.checkBoundsIndexes(destinationOffset, destinationOffset + endIndex - startIndex, destination.size) + + var dstIndex = destinationOffset + for (index in startIndex until endIndex) { + destination[dstIndex++] = string[index] + } + } + + /** + * Appends characters in a subarray of the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at specified [startIndex]. + * + * @param value the array from which characters are appended. + * @param startIndex the beginning (inclusive) of the subarray to append. + * @param endIndex the end (exclusive) of the subarray to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun appendRange(value: CharArray, startIndex: Int, endIndex: Int): StringBuilder { + string += value.concatToString(startIndex, endIndex) + return this + } + + /** + * Appends a subsequence of the specified character sequence [value] to this string builder and returns this instance. + * + * @param value the character sequence from which a subsequence is appended. If [value] is `null`, + * then characters are appended as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to append. + * @param endIndex the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun appendRange(value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder { + val stringCsq = value.toString() + AbstractList.checkBoundsIndexes(startIndex, endIndex, stringCsq.length) + + string += stringCsq.substring(startIndex, endIndex) + return this + } + + /** + * Inserts characters in a subarray of the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] array, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the array from which characters are inserted. + * @param startIndex the beginning (inclusive) of the subarray to insert. + * @param endIndex the end (exclusive) of the subarray to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun insertRange(index: Int, value: CharArray, startIndex: Int, endIndex: Int): StringBuilder { + AbstractList.checkPositionIndex(index, this.length) + + string = string.substring(0, index) + value.concatToString(startIndex, endIndex) + string.substring(index) + return this + } + + /** + * Inserts characters in a subsequence of the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which a subsequence is inserted. If [value] is `null`, + * then characters will be inserted as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to insert. + * @param endIndex the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + public fun insertRange(index: Int, value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder { + AbstractList.checkPositionIndex(index, length) + + val stringCsq = value.toString() + AbstractList.checkBoundsIndexes(startIndex, endIndex, stringCsq.length) + + string = string.substring(0, index) + stringCsq.substring(startIndex, endIndex) + string.substring(index) + return this + } } - /** - * Clears the content of this string builder making it empty. + * Clears the content of this string builder making it empty and returns this instance. * * @sample samples.text.Strings.clearStringBuilder */ @SinceKotlin("1.3") @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") public actual inline fun StringBuilder.clear(): StringBuilder = this.clear() + +/** + * Sets the character at the specified [index] to the specified [value]. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline operator fun StringBuilder.set(index: Int, value: Char) = this.set(index, value) + +/** + * Replaces characters in the specified range of this string builder with characters in the specified string [value] and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to replace. + * @param endIndex the end (exclusive) of the range to replace. + * @param value the string to replace with. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [startIndex] is less than zero, greater than the length of this string builder, or `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.setRange(startIndex: Int, endIndex: Int, value: String): StringBuilder = + this.setRange(startIndex, endIndex, value) + +/** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.deleteAt(index: Int): StringBuilder = this.deleteAt(index) + +/** + * Removes characters in the specified range from this string builder and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to remove. + * @param endIndex the end (exclusive) of the range to remove. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.deleteRange(startIndex: Int, endIndex: Int): StringBuilder = this.deleteRange(startIndex, endIndex) + +/** + * Copies characters from this string builder into the [destination] character array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to, 0 by default. + * @param startIndex the beginning (inclusive) of the range to copy, 0 by default. + * @param endIndex the end (exclusive) of the range to copy, length of this string builder by default. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +public actual inline fun StringBuilder.toCharArray(destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = this.length) = + this.toCharArray(destination, destinationOffset, startIndex, endIndex) + +/** + * Appends characters in a subarray of the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at specified [startIndex]. + * + * @param value the array from which characters are appended. + * @param startIndex the beginning (inclusive) of the subarray to append. + * @param endIndex the end (exclusive) of the subarray to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.appendRange(value: CharArray, startIndex: Int, endIndex: Int): StringBuilder = + this.appendRange(value, startIndex, endIndex) + +/** + * Appends a subsequence of the specified character sequence [value] to this string builder and returns this instance. + * + * @param value the character sequence from which a subsequence is appended. If [value] is `null`, + * then characters are appended as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to append. + * @param endIndex the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.appendRange(value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder = + this.appendRange(value, startIndex, endIndex) + +/** + * Inserts characters in a subarray of the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] array, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the array from which characters are inserted. + * @param startIndex the beginning (inclusive) of the subarray to insert. + * @param endIndex the end (exclusive) of the subarray to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.insertRange(index: Int, value: CharArray, startIndex: Int, endIndex: Int): StringBuilder = + this.insertRange(index, value, startIndex, endIndex) + +/** + * Inserts characters in a subsequence of the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which a subsequence is inserted. If [value] is `null`, + * then characters will be inserted as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to insert. + * @param endIndex the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "NOTHING_TO_INLINE") +public actual inline fun StringBuilder.insertRange(index: Int, value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder = + this.insertRange(index, value, startIndex, endIndex) diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringBuilderJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringBuilderJVM.kt index bca382f7557..e7f43e5cb59 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringBuilderJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringBuilderJVM.kt @@ -9,19 +9,155 @@ package kotlin.text /** - * Sets the character at the specified [index] to the specified [value]. - */ -@kotlin.internal.InlineOnly -public inline operator fun StringBuilder.set(index: Int, value: Char): Unit = this.setCharAt(index, value) - -/** - * Clears the content of this string builder making it empty. + * Clears the content of this string builder making it empty and returns this instance. * * @sample samples.text.Strings.clearStringBuilder */ @SinceKotlin("1.3") public actual fun StringBuilder.clear(): StringBuilder = apply { setLength(0) } +/** + * Sets the character at the specified [index] to the specified [value]. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@kotlin.internal.InlineOnly +public actual inline operator fun StringBuilder.set(index: Int, value: Char): Unit = this.setCharAt(index, value) + +/** + * Replaces characters in the specified range of this string builder with characters in the specified string [value] and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to replace. + * @param endIndex the end (exclusive) of the range to replace. + * @param value the string to replace with. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [startIndex] is less than zero, greater than the length of this string builder, or `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.setRange(startIndex: Int, endIndex: Int, value: String): StringBuilder = + this.replace(startIndex, endIndex, value) + +/** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.deleteAt(index: Int): StringBuilder = this.deleteCharAt(index) + +/** + * Removes characters in the specified range from this string builder and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to remove. + * @param endIndex the end (exclusive) of the range to remove. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.deleteRange(startIndex: Int, endIndex: Int): StringBuilder = this.delete(startIndex, endIndex) + +/** + * Copies characters from this string builder into the [destination] character array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to, 0 by default. + * @param startIndex the beginning (inclusive) of the range to copy, 0 by default. + * @param endIndex the end (exclusive) of the range to copy, length of this string builder by default. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +public actual inline fun StringBuilder.toCharArray(destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = this.length) = + this.getChars(startIndex, endIndex, destination, destinationOffset) + +/** + * Appends characters in a subarray of the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at specified [startIndex]. + * + * @param value the array from which characters are appended. + * @param startIndex the beginning (inclusive) of the subarray to append. + * @param endIndex the end (exclusive) of the subarray to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.appendRange(value: CharArray, startIndex: Int, endIndex: Int): StringBuilder = + this.append(value, startIndex, endIndex - startIndex) + +/** + * Appends a subsequence of the specified character sequence [value] to this string builder and returns this instance. + * + * @param value the character sequence from which a subsequence is appended. If [value] is `null`, + * then characters are appended as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to append. + * @param endIndex the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.appendRange(value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder = + this.append(value, startIndex, endIndex) + +/** + * Inserts characters in a subarray of the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] array, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the array from which characters are inserted. + * @param startIndex the beginning (inclusive) of the subarray to insert. + * @param endIndex the end (exclusive) of the subarray to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.insertRange(index: Int, value: CharArray, startIndex: Int, endIndex: Int): StringBuilder = + this.insert(index, value, startIndex, endIndex - startIndex) + +/** + * Inserts characters in a subsequence of the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which a subsequence is inserted. If [value] is `null`, + * then characters will be inserted as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to insert. + * @param endIndex the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun StringBuilder.insertRange(index: Int, value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder = + this.insert(index, value, startIndex, endIndex) + + private object SystemProperties { /** Line separator for current system. */ @JvmField diff --git a/libraries/stdlib/jvm/test/text/StringBuilderJVMTest.kt b/libraries/stdlib/jvm/test/text/StringBuilderJVMTest.kt deleted file mode 100644 index 81bf91ead29..00000000000 --- a/libraries/stdlib/jvm/test/text/StringBuilderJVMTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package test.text - -import kotlin.test.* - -class StringBuilderJVMTest() { - - @Test fun stringBuildWithInitialCapacity() { - val s = buildString(123) { - assertEquals(123, capacity()) - } - assertEquals("", s) - } - - @Test fun getAndSetChar() { - val sb = StringBuilder("abc") - sb[1] = 'z' - - assertEquals("azc", sb.toString()) - assertEquals('c', sb[2]) - } -} diff --git a/libraries/stdlib/src/kotlin/text/StringBuilder.kt b/libraries/stdlib/src/kotlin/text/StringBuilder.kt index b60fd020004..8e4c203e6f9 100644 --- a/libraries/stdlib/src/kotlin/text/StringBuilder.kt +++ b/libraries/stdlib/src/kotlin/text/StringBuilder.kt @@ -8,30 +8,392 @@ package kotlin.text +/** + * A mutable sequence of characters. + * + * String builder can be used to efficiently perform multiple string manipulation operations. + */ expect class StringBuilder : Appendable, CharSequence { + /** Constructs an empty string builder. */ constructor() + + /** Constructs an empty string builder with the specified initial [capacity]. */ constructor(capacity: Int) + + /** Constructs a string builder that contains the same characters as the specified [content] char sequence. */ constructor(content: CharSequence) + /** Constructs a string builder that contains the same characters as the specified [content] string. */ + @SinceKotlin("1.3") +// @ExperimentalStdlibApi + constructor(content: String) + override val length: Int + override operator fun get(index: Int): Char + override fun subSequence(startIndex: Int, endIndex: Int): CharSequence - fun reverse(): StringBuilder override fun append(c: Char): StringBuilder override fun append(csq: CharSequence?): StringBuilder override fun append(csq: CharSequence?, start: Int, end: Int): StringBuilder - fun append(obj: Any?): StringBuilder + + /** + * Reverses the contents of this string builder and returns this instance. + * + * Surrogate pairs included in this string builder are treated as single characters. + * Therefore, the order of the high-low surrogates is never reversed. + * + * Note that the reverse operation may produce new surrogate pairs that were unpaired low-surrogates and high-surrogates before the operation. + * For example, reversing `"\uDC00\uD800"` produces `"\uD800\uDC00"` which is a valid surrogate pair. + */ + fun reverse(): StringBuilder + + /** + * Appends the string representation of the specified object [value] to this string builder and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was appended to this string builder. + */ + fun append(value: Any?): StringBuilder + + /** + * Appends the string representation of the specified boolean [value] to this string builder and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was appended to this string builder. + */ + @SinceKotlin("1.3") +// @ExperimentalStdlibApi + fun append(value: Boolean): StringBuilder + + /** + * Appends characters in the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at the index 0. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun append(value: CharArray): StringBuilder + + /** + * Appends the specified string [value] to this string builder and returns this instance. + */ + @SinceKotlin("1.3") +// @ExperimentalStdlibApi + fun append(value: String): StringBuilder + + /** + * Returns the current capacity of this string builder. + * + * The capacity is the maximum length this string builder can have before an allocation occurs. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun capacity(): Int + + /** + * Ensures that the capacity of this string builder is at least equal to the specified [minimumCapacity]. + * + * If the current capacity is less than the [minimumCapacity], a new backing storage is allocated with greater capacity. + * Otherwise, this method takes no action and simply returns. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun ensureCapacity(minimumCapacity: Int) + + /** + * Returns the index within this string builder of the first occurrence of the specified [string]. + * + * Returns `-1` if the specified [string] does not occur in this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun indexOf(string: String): Int + + /** + * Returns the index within this string builder of the first occurrence of the specified [string], + * starting at the specified [startIndex]. + * + * Returns `-1` if the specified [string] does not occur in this string builder starting at the specified [startIndex]. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun indexOf(string: String, startIndex: Int): Int + + /** + * Returns the index within this string builder of the last occurrence of the specified [string]. + * The last occurrence of empty string `""` is considered to be at the index equal to `this.length`. + * + * Returns `-1` if the specified [string] does not occur in this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun lastIndexOf(string: String): Int + + /** + * Returns the index within this string builder of the last occurrence of the specified [string], + * starting from the specified [startIndex] toward the beginning. + * + * Returns `-1` if the specified [string] does not occur in this string builder starting at the specified [startIndex]. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun lastIndexOf(string: String, startIndex: Int): Int + + /** + * Inserts the string representation of the specified boolean [value] into this string builder at the specified [index] and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was inserted into this string builder at the specified [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insert(index: Int, value: Boolean): StringBuilder + + /** + * Inserts the specified character [value] into this string builder at the specified [index] and returns this instance. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insert(index: Int, value: Char): StringBuilder + + /** + * Inserts characters in the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] character array, starting at [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insert(index: Int, value: CharArray): StringBuilder + + /** + * Inserts characters in the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which characters are inserted. If [value] is `null`, then the four characters `"null"` are inserted. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insert(index: Int, value: CharSequence?): StringBuilder + + /** + * Inserts the string representation of the specified object [value] into this string builder at the specified [index] and returns this instance. + * + * The overall effect is exactly as if the [value] were converted to a string by the `value.toString()` method, + * and then that string was inserted into this string builder at the specified [index]. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insert(index: Int, value: Any?): StringBuilder + + /** + * Inserts the string [value] into this string builder at the specified [index] and returns this instance. + * + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun insert(index: Int, value: String): StringBuilder + + /** + * Sets the length of this string builder to the specified [newLength]. + * + * If the [newLength] is less than the current length, it is changed to the specified [newLength]. + * Otherwise, null characters '\u0000' are appended to this string builder until its length is less than the [newLength]. + * + * Note that in Kotlin/JS [set] operator function has non-constant execution time complexity. + * Therefore, increasing length of this string builder and then updating each character by index may slow down your program. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [newLength] is less than zero. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun setLength(newLength: Int) + + /** + * Returns a new [String] that contains characters in this string builder at [startIndex] (inclusive) and up to the [length] (exclusive). + * + * @throws IndexOutOfBoundsException if [startIndex] is less than zero or greater than the length of this string builder. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun substring(startIndex: Int): String + + /** + * Returns a new [String] that contains characters in this string builder at [startIndex] (inclusive) and up to the [endIndex] (exclusive). + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun substring(startIndex: Int, endIndex: Int): String + + /** + * Attempts to reduce storage used for this string builder. + * + * If the backing storage of this string builder is larger than necessary to hold its current contents, + * then it may be resized to become more space efficient. + * Calling this method may, but is not required to, affect the value of the [capacity] property. + */ + @SinceKotlin("1.3") + @ExperimentalStdlibApi + fun trimToSize() } + /** - * Clears the content of this string builder making it empty. + * Clears the content of this string builder making it empty and returns this instance. * * @sample samples.text.Strings.clearStringBuilder */ @SinceKotlin("1.3") public expect fun StringBuilder.clear(): StringBuilder +/** + * Sets the character at the specified [index] to the specified [value]. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect operator fun StringBuilder.set(index: Int, value: Char) + +/** + * Replaces characters in the specified range of this string builder with characters in the specified string [value] and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to replace. + * @param endIndex the end (exclusive) of the range to replace. + * @param value the string to replace with. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] if [startIndex] is less than zero, greater than the length of this string builder, or `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.setRange(startIndex: Int, endIndex: Int, value: String): StringBuilder + +/** + * Removes the character at the specified [index] from this string builder and returns this instance. + * + * If the `Char` at the specified [index] is part of a supplementary code point, this method does not remove the entire supplementary character. + * + * @param index the index of `Char` to remove. + * + * @throws IndexOutOfBoundsException if [index] is out of bounds of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.deleteAt(index: Int): StringBuilder + +/** + * Removes characters in the specified range from this string builder and returns this instance. + * + * @param startIndex the beginning (inclusive) of the range to remove. + * @param endIndex the end (exclusive) of the range to remove. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.deleteRange(startIndex: Int, endIndex: Int): StringBuilder + +/** + * Copies characters from this string builder into the [destination] character array. + * + * @param destination the array to copy to. + * @param destinationOffset the position in the array to copy to, 0 by default. + * @param startIndex the beginning (inclusive) of the range to copy, 0 by default. + * @param endIndex the end (exclusive) of the range to copy, length of this string builder by default. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of this string builder indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException when the subrange doesn't fit into the [destination] array starting at the specified [destinationOffset], + * or when that index is out of the [destination] array indices range. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.toCharArray(destination: CharArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = this.length) + +/** + * Appends characters in a subarray of the specified character array [value] to this string builder and returns this instance. + * + * Characters are appended in order, starting at specified [startIndex]. + * + * @param value the array from which characters are appended. + * @param startIndex the beginning (inclusive) of the subarray to append. + * @param endIndex the end (exclusive) of the subarray to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.appendRange(value: CharArray, startIndex: Int, endIndex: Int): StringBuilder + +/** + * Appends a subsequence of the specified character sequence [value] to this string builder and returns this instance. + * + * @param value the character sequence from which a subsequence is appended. If [value] is `null`, + * then characters are appended as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to append. + * @param endIndex the end (exclusive) of the subsequence to append. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.appendRange(value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder + +/** + * Inserts characters in a subarray of the specified character array [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in same order as in the [value] array, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the array from which characters are inserted. + * @param startIndex the beginning (inclusive) of the subarray to insert. + * @param endIndex the end (exclusive) of the subarray to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] array indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.insertRange(index: Int, value: CharArray, startIndex: Int, endIndex: Int): StringBuilder + +/** + * Inserts characters in a subsequence of the specified character sequence [value] into this string builder at the specified [index] and returns this instance. + * + * The inserted characters go in the same order as in the [value] character sequence, starting at [index]. + * + * @param index the position in this string builder to insert at. + * @param value the character sequence from which a subsequence is inserted. If [value] is `null`, + * then characters will be inserted as if [value] contained the four characters `"null"`. + * @param startIndex the beginning (inclusive) of the subsequence to insert. + * @param endIndex the end (exclusive) of the subsequence to insert. + * + * @throws IndexOutOfBoundsException or [IllegalArgumentException] when [startIndex] or [endIndex] is out of range of the [value] character sequence indices or when `startIndex > endIndex`. + * @throws IndexOutOfBoundsException if [index] is less than zero or greater than the length of this string builder. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun StringBuilder.insertRange(index: Int, value: CharSequence?, startIndex: Int, endIndex: Int): StringBuilder + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Use append(value: Any?) instead", ReplaceWith("append(value = obj)"), DeprecationLevel.WARNING) +@kotlin.internal.InlineOnly +public inline fun StringBuilder.append(obj: Any?): StringBuilder = this.append(obj) + /** * Builds new string by populating newly created [StringBuilder] using provided [builderAction] * and then converting it to [String]. diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index e82b9002dcd..1851a953a4e 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -438,13 +438,14 @@ public fun String.substringAfterLast(delimiter: String, missingDelimiterValue: S * @param startIndex the index of the first character to be replaced. * @param endIndex the index of the first character after the replacement to keep in the string. */ +@UseExperimental(ExperimentalStdlibApi::class) public fun CharSequence.replaceRange(startIndex: Int, endIndex: Int, replacement: CharSequence): CharSequence { if (endIndex < startIndex) throw IndexOutOfBoundsException("End index ($endIndex) is less than start index ($startIndex).") val sb = StringBuilder() - sb.append(this, 0, startIndex) + sb.appendRange(this, 0, startIndex) sb.append(replacement) - sb.append(this, endIndex, length) + sb.appendRange(this, endIndex, length) return sb } @@ -483,6 +484,7 @@ public inline fun String.replaceRange(range: IntRange, replacement: CharSequence * * [endIndex] is not included in the removed part. */ +@UseExperimental(ExperimentalStdlibApi::class) public fun CharSequence.removeRange(startIndex: Int, endIndex: Int): CharSequence { if (endIndex < startIndex) throw IndexOutOfBoundsException("End index ($endIndex) is less than start index ($startIndex).") @@ -491,8 +493,8 @@ public fun CharSequence.removeRange(startIndex: Int, endIndex: Int): CharSequenc return this.subSequence(0, length) val sb = StringBuilder(length - (endIndex - startIndex)) - sb.append(this, 0, startIndex) - sb.append(this, endIndex, length) + sb.appendRange(this, 0, startIndex) + sb.appendRange(this, endIndex, length) return sb } diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index 8c889de539a..0c39d62c36c 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -302,6 +302,7 @@ public inline class Duration internal constructor(internal val value: Double) : * * @sample samples.time.Durations.toIsoString */ + @UseExperimental(ExperimentalStdlibApi::class) public fun toIsoString(): String = buildString { if (isNegative()) append('-') append("PT") @@ -321,8 +322,8 @@ public inline class Duration internal constructor(internal val value: Double) : append('.') val nss = nanoseconds.toString().padStart(9, '0') when { - nanoseconds % 1_000_000 == 0 -> append(nss, 0, 3) - nanoseconds % 1_000 == 0 -> append(nss, 0, 6) + nanoseconds % 1_000_000 == 0 -> appendRange(nss, 0, 3) + nanoseconds % 1_000 == 0 -> appendRange(nss, 0, 6) else -> append(nss) } } diff --git a/libraries/stdlib/test/text/StringBuilderTest.kt b/libraries/stdlib/test/text/StringBuilderTest.kt index 545be6cfac0..e18b2e3d7b2 100644 --- a/libraries/stdlib/test/text/StringBuilderTest.kt +++ b/libraries/stdlib/test/text/StringBuilderTest.kt @@ -5,7 +5,9 @@ package test.text +import kotlin.random.Random import kotlin.test.* +import kotlin.text.* class StringBuilderTest { @@ -26,7 +28,7 @@ class StringBuilderTest { @Test fun append() { // this test is needed for JS implementation assertEquals("em", buildString { - append("element", 2, 4) + appendRange("element", 2, 4) }) } @@ -87,4 +89,398 @@ class StringBuilderTest { assertFailsWith { assertEquals('t', sb[-1]) } assertFailsWith { assertEquals('t', sb[4]) } } + + @Test + fun reverse() { + StringBuilder("my reverse test").let { sb -> + sb.reverse() + assertEquals("tset esrever ym", sb.toString()) + + sb.append('\uD800') + sb.append('\uDC00') + + sb.insert(10, "\uDC01\uD801") + sb.insert(0, "\uD802\uDC02") + + sb.reverse() + assertEquals("\uD800\uDC00my re\uD801\uDC01verse test\uD802\uDC02", sb.toString()) + } + } + + @Test + fun appendChar() { + val times = 100 + val expected = "a".repeat(times) + + val sb = StringBuilder() + repeat(times) { sb.append('a') } + assertEquals(expected, sb.toString()) + + sb.append('\uD800') + sb.append('\uDC00') + assertEquals(expected + "\uD800\uDC00", sb.toString()) + } + + @Test + fun appendInt() { + val times = 100 + val expected = (0 until times).fold("") { res, idx -> res + idx } + + val sb = StringBuilder() + repeat(times) { sb.append(it) } + assertEquals(expected, sb.toString()) + + val cornerCase = listOf(0, -1, Int.MIN_VALUE, Int.MAX_VALUE) + val expectedCornerCase = cornerCase.fold("") { res, e -> res + e } + for (int in cornerCase) sb.append(int) + assertEquals(expected + expectedCornerCase, sb.toString()) + } + + @Test + fun appendBoolean() { + StringBuilder().append(true).append(false).append(true).append(true).let { sb -> + assertEquals("truefalsetruetrue", sb.toString()) + } + } + + + @Test + fun appendString() { + val times = 100 + val expected = "foo".repeat(times) + + StringBuilder().let { sb -> + repeat(times) { sb.append("foo") } + assertEquals(expected, sb.toString()) + } + } + + @Test + fun appendAny() { + val myAny = object { + override fun toString(): String = "It's My Any!" + } + + StringBuilder().let { sb -> + sb.append(null as Any?) + sb.append(myAny) + assertEquals("nullIt's My Any!", sb.toString()) + } + } + + @Test + fun appendCharArray() { + StringBuilder().let { sb -> + val times = 100 + val expected = "foo".repeat(times) + + repeat(times) { sb.append(charArrayOf('f', 'o', 'o')) } + assertEquals(expected, sb.toString()) + } + + StringBuilder().let { sb -> + val charArray = charArrayOf( + 'm', 'y', ' ', 'a', 'p', 'p', 'e', 'n', 'd', ' ', 'c', 'h', 'a', 'r', ' ', 'a', 'r', 'r', 'a', 'y', ' ', 't', 'e', 's', 't' + ) + + String(charArray, 0, 1) + + sb.appendRange(charArray, 0, charArray.size /*25*/) + sb.appendRange(charArray, 0, 9) + sb.appendRange(charArray, 15, 25) + sb.appendRange(charArray, 9, 15) + + assertEquals("my append char array testmy appendarray test char ", sb.toString()) + + assertFails { sb.appendRange(charArrayOf('_', '*', '#'), -1, 0) } + assertFails { sb.appendRange(charArrayOf('_', '*', '#'), 0, 4) } + assertFails { sb.appendRange(charArrayOf('_', '*', '#'), 2, 1) } + assertFails { sb.appendRange(charArrayOf('_', '*', '#'), 2, -1) } + } + } + + @Test + fun deleteChar() { + StringBuilder("my delete test").let { sb -> + sb.deleteAt(0) + assertEquals("y delete test", sb.toString()) + sb.deleteAt(5) + assertEquals("y delte test", sb.toString()) + sb.deleteAt(11) + assertEquals("y delte tes", sb.toString()) + + assertFailsWith { sb.deleteAt(11) } + assertFailsWith { sb.deleteAt(-1) } + } + } + + @Test + fun deleteSubstring() { + StringBuilder("my delete substring test").let { sb -> + sb.deleteRange(0, 2) + assertEquals(" delete substring test", sb.toString()) + sb.deleteRange(7, 17) + assertEquals(" delete test", sb.toString()) + sb.deleteRange(8, 12) + assertEquals(" delete ", sb.toString()) + sb.deleteRange(8, 12) + assertEquals(" delete ", sb.toString()) + + assertFails { sb.deleteRange(-1, 1) } + assertFails { sb.deleteRange(0, -1) } + assertFails { sb.deleteRange(2, 1) } + assertFails { sb.deleteRange(sb.length + 1, sb.length + 2) } + } + } + + @Test + fun capacityTest() { + assertEquals(100, StringBuilder(100).capacity()) + + StringBuilder("string builder from string capacity test").let { sb -> + assertTrue(sb.capacity() >= sb.length) + } + + StringBuilder().let { sb -> + assertTrue(sb.capacity() >= sb.length) + repeat(Random.nextInt(17, 30)) { sb.append('c') } + assertTrue(sb.capacity() >= sb.length) + repeat(Random.nextInt(35, 62)) { sb.insert(0, "s") } + sb.ensureCapacity(1) + assertTrue(sb.capacity() >= sb.length) + sb.ensureCapacity(sb.length * 10) + assertTrue(sb.capacity() >= sb.length * 10) + } + } + + @Test + fun indexOf() { + StringBuilder("my indexOf test").let { sb -> + assertEquals(0, sb.indexOf("")) + assertEquals(5, sb.indexOf("", 5)) + assertEquals(sb.length, sb.indexOf("", sb.length)) + assertEquals(sb.length, sb.indexOf("", 100)) // Java implementation, should be -1 + + assertEquals(6, sb.indexOf("e")) + assertEquals(12, sb.indexOf("e", 7)) + assertEquals(-1, sb.indexOf("e", 13)) + assertEquals(-1, sb.indexOf("e", 100)) + + assertEquals(11, sb.indexOf("test")) + assertEquals(11, sb.indexOf("test", 11)) + assertEquals(-1, sb.indexOf("test", 12)) + } + } + + @Test + fun lastIndexOf() { + StringBuilder("my lastIndexOf test").let { sb -> + assertEquals(sb.length, sb.lastIndexOf("")) + assertEquals(sb.length, sb.lastIndexOf("", 100)) + assertEquals(5, sb.lastIndexOf("", 5)) + assertEquals(0, sb.lastIndexOf("", 0)) + assertEquals(-1, sb.lastIndexOf("", -100)) + + assertEquals(16, sb.lastIndexOf("e")) + assertEquals(10, sb.lastIndexOf("e", 15)) + assertEquals(-1, sb.lastIndexOf("e", 9)) + assertEquals(-1, sb.lastIndexOf("e", -100)) + + assertEquals(15, sb.lastIndexOf("test")) + assertEquals(15, sb.lastIndexOf("test", 15)) + assertEquals(-1, sb.lastIndexOf("test", 14)) + } + } + + @Test + fun insertBoolean() { + StringBuilder().let { sb -> + sb.insert(0, true) + assertEquals("true", sb.toString()) + sb.insert(0, false) + assertEquals("falsetrue", sb.toString()) + sb.insert(5, true) + assertEquals("falsetruetrue", sb.toString()) + + assertFailsWith { sb.insert(-1, false) } + assertFailsWith { sb.insert(sb.length + 1, false) } + } + } + + @Test + fun insertChar() { + StringBuilder("my insert char test").let { sb -> + sb.insert(0, '_') + assertEquals("_my insert char test", sb.toString()) + sb.insert(10, 'T') + assertEquals("_my insertT char test", sb.toString()) + sb.insert(21, '_') + assertEquals("_my insertT char test_", sb.toString()) + + assertFailsWith { sb.insert(-1, '_') } + assertFailsWith { sb.insert(sb.length + 1, '_') } + } + } + + @Test + fun insertCharArray() { + StringBuilder("my insert CharArray test").let { sb -> + sb.insert(0, charArrayOf('_')) + assertEquals("_my insert CharArray test", sb.toString()) + sb.insert(10, charArrayOf('T')) + assertEquals("_my insertT CharArray test", sb.toString()) + sb.insertRange(26, charArrayOf('_', '*', '#'), 0, 1) + assertEquals("_my insertT CharArray test_", sb.toString()) + + assertFailsWith { sb.insert(-1, charArrayOf('_')) } + assertFailsWith { sb.insert(sb.length + 1, charArrayOf('_')) } + assertFails { sb.insertRange(0, charArrayOf('_', '*', '#'), -1, 0) } + assertFails { sb.insertRange(0, charArrayOf('_', '*', '#'), 0, 4) } + assertFails { sb.insertRange(0, charArrayOf('_', '*', '#'), 2, 1) } + assertFails { sb.insertRange(0, charArrayOf('_', '*', '#'), 2, -1) } + } + } + + @Test + fun insertCharSequence() { + StringBuilder("my insert CharSequence test").let { sb -> + sb.insert(0, "MMM" as CharSequence) + assertEquals("MMMmy insert CharSequence test", sb.toString()) + sb.insert(12, StringBuilder("T")) + assertEquals("MMMmy insertT CharSequence test", sb.toString()) + sb.insertRange(31, "_*#", 0, 1) + assertEquals("MMMmy insertT CharSequence test_", sb.toString()) + sb.insertRange(0, null as CharSequence?, 0, 2) + assertEquals("nuMMMmy insertT CharSequence test_", sb.toString()) + + assertFailsWith { sb.insert(-1, "_" as CharSequence) } + assertFailsWith { sb.insert(sb.length + 1, StringBuilder("_")) } + assertFails { sb.insertRange(0, null as CharSequence?, -1, 0) } + assertFails { sb.insertRange(0, null as CharSequence?, 0, 5) } + assertFails { sb.insertRange(0, null as CharSequence?, 2, 1) } + } + } + + @Test + fun insertAny() { + val myAny = object { + override fun toString(): String = "It's My Any!" + } + + StringBuilder().let { sb -> + sb.insert(0, null as Any?) + sb.insert(2, myAny) + assertEquals("nuIt's My Any!ll", sb.toString()) + } + } + + @Test + fun insertString() { + StringBuilder("my insert string test").let { sb -> + sb.insert(0, "_") + assertEquals("_my insert string test", sb.toString()) + sb.insert(10, "TtT") + assertEquals("_my insertTtT string test", sb.toString()) + sb.insert(25, "_!_") + assertEquals("_my insertTtT string test_!_", sb.toString()) + } + } + + @Test + fun setLength() { + StringBuilder("my setLength test").let { sb -> + sb.setLength(17) + assertEquals("my setLength test", sb.toString()) + sb.setLength(0) + assertEquals("", sb.toString()) + sb.setLength(5) + assertEquals("\u0000\u0000\u0000\u0000\u0000", sb.toString()) + + assertFails { sb.setLength(-1) } + } + } + + @Test + fun substring() { + StringBuilder("my substring test").let { sb -> + assertEquals("my ", sb.substring(0, 3)) + assertEquals("substring", sb.substring(3, 12)) + assertEquals("ing test", sb.substring(9)) + assertEquals("ing test", sb.substring(9, 17)) + + assertFails { sb.substring(-1) } + assertFails { sb.substring(0, -1) } + assertFails { sb.substring(0, sb.length + 1) } + assertFails { sb.substring(2, 1) } + } + } + + @Test + fun trimToSize() { + StringBuilder("my trimToSize test").let { sb -> + assertEquals(18, sb.length) + assertTrue(sb.capacity() >= sb.length) + sb.append('1') + sb.trimToSize() + assertEquals(19, sb.length) + assertTrue(sb.capacity() >= sb.length) + } + } + + @Test + fun set() { + StringBuilder("my set test").let { sb -> + sb[0] = 'M' + assertEquals("My set test", sb.toString()) + sb[2] = 'm' + assertEquals("Mymset test", sb.toString()) + sb[10] = 'T' + assertEquals("Mymset tesT", sb.toString()) + + assertFailsWith { sb[-1] = '_' } + assertFailsWith { sb[sb.length] = '_' } + } + } + + @Test + fun setRange() { + StringBuilder("my replace test").let { sb -> + sb.setRange(0, 4, "R") + assertEquals("Replace test", sb.toString()) + sb.setRange(7, 7, " empty string") + assertEquals("Replace empty string test", sb.toString()) + sb.setRange(20, 25, "") + assertEquals("Replace empty string", sb.toString()) + sb.setRange(20, 25, "") + assertEquals("Replace empty string", sb.toString()) + + assertFails { sb.setRange(-1, 0, "") } + assertFails { sb.setRange(0, -1, "") } + assertFails { sb.setRange(2, 1, "") } + assertFails { sb.setRange(sb.length + 1, sb.length + 2, "") } + } + } + + @Test + fun toCharArray() { + StringBuilder("my toCharArray test").let { sb -> + val chars = CharArray(10) { '_' } + + sb.toCharArray(chars, 8, 0, 2) + assertEquals("________my", String(chars)) + sb.toCharArray(chars, 3, 6, 11) + assertEquals("___harArmy", String(chars)) + sb.toCharArray(chars, 0, 16, 19) + assertEquals("estharArmy", String(chars)) + + sb.setLength(5) + + assertFails { sb.toCharArray(chars, -1, 0, 1) } + assertFails { sb.toCharArray(chars, chars.size, 0, 1) } + assertFails { sb.toCharArray(chars, 0, -1, 0) } + assertFails { sb.toCharArray(chars, 0, 0, -1) } + assertFails { sb.toCharArray(chars, 0, 2, 1) } + assertFails { sb.toCharArray(chars, 0, 0, sb.length + 1) } + } + } }