From fafb4a791481be3e440298a7add46bc206623200 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Tue, 29 Sep 2020 20:04:56 +0300 Subject: [PATCH] Use regex for case-insensitive String.replace KT-41799 --- .../stdlib/jvm/src/kotlin/text/StringsJVM.kt | 47 +++++++++++-------- libraries/stdlib/test/text/StringTest.kt | 1 + 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt index 8bae08ac8fe..e1e6cc947cc 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt @@ -77,27 +77,36 @@ public actual fun String.replace(oldChar: Char, newChar: Char, ignoreCase: Boole */ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun String.replace(oldValue: String, newValue: String, ignoreCase: Boolean = false): String { - // prefer case-insensitive platform implementation - if (!ignoreCase) return (this as java.lang.String).replace(oldValue, newValue) + if (ignoreCase) { + val matcher = Pattern.compile(oldValue, Pattern.LITERAL or Pattern.CASE_INSENSITIVE).matcher(this) + if (!matcher.find()) return this + val stringBuilder = StringBuilder() + var i = 0 + do { + stringBuilder.append(this, i, matcher.start()).append(newValue) + i = matcher.end() + } while (matcher.find()) + return stringBuilder.append(this, i, length).toString() + } else { + var occurrenceIndex: Int = indexOf(oldValue, 0, ignoreCase) + // FAST PATH: no match + if (occurrenceIndex < 0) return this - var occurrenceIndex: Int = indexOf(oldValue, 0, ignoreCase) - // FAST PATH: no match - if (occurrenceIndex < 0) return this + val oldValueLength = oldValue.length + val searchStep = oldValueLength.coerceAtLeast(1) + val newLengthHint = length - oldValueLength + newValue.length + if (newLengthHint < 0) throw OutOfMemoryError() + val stringBuilder = StringBuilder(newLengthHint) - val oldValueLength = oldValue.length - val searchStep = oldValueLength.coerceAtLeast(1) - val newLengthHint = length - oldValueLength + newValue.length - if (newLengthHint < 0) throw OutOfMemoryError() - val stringBuilder = StringBuilder(newLengthHint) - - var i = 0 - do { - stringBuilder.append(this, i, occurrenceIndex).append(newValue) - i = occurrenceIndex + oldValueLength - if (occurrenceIndex >= length) break - occurrenceIndex = indexOf(oldValue, occurrenceIndex + searchStep, ignoreCase) - } while (occurrenceIndex > 0) - return stringBuilder.append(this, i, length).toString() + var i = 0 + do { + stringBuilder.append(this, i, occurrenceIndex).append(newValue) + i = occurrenceIndex + oldValueLength + if (occurrenceIndex >= length) break + occurrenceIndex = indexOf(oldValue, occurrenceIndex + searchStep, ignoreCase) + } while (occurrenceIndex > 0) + return stringBuilder.append(this, i, length).toString() + } } /** diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 8c92fe7513a..c57555b06c6 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -895,6 +895,7 @@ class StringTest { assertEquals("/b/", input.replace("ab", "/", ignoreCase = true)) assertEquals("-a-b-b-A-b-", input.replace("", "-")) + assertEquals("-a-b-b-A-b-", input.replace("", "-", ignoreCase = true)) } @Test fun replaceFirst() {