From af31794f605426cb014161d79e6201a0a309f09d Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Thu, 7 Feb 2019 11:36:09 -0500 Subject: [PATCH] Add Locale-accepting overloads for (de)capitalize in JDK stdlib #KT-28933 fixed --- .../stdlib/jvm/src/kotlin/text/StringsJVM.kt | 35 +++++++++++++++++++ .../stdlib/jvm/test/text/StringJVMTest.kt | 28 +++++++++++++++ .../kotlin-stdlib-runtime-merged.txt | 2 ++ 3 files changed, 65 insertions(+) diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt index d7b99960bff..d5d58582be1 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt @@ -567,6 +567,31 @@ public actual fun String.capitalize(): String { return if (isNotEmpty() && this[0].isLowerCase()) substring(0, 1).toUpperCase() + substring(1) else this } +/** + * Returns a copy of this string having its first letter uppercased preferring [Character.toTitleCase] (if different from + * [Character.toUpperCase]) or by [String.toUpperCase] using [locale], or the original string, if it's empty or already + * starts with an upper case letter. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public fun String.capitalize(locale: Locale): String { + if (isNotEmpty()) { + val firstChar = this[0] + if (firstChar.isLowerCase()) { + return buildString { + val titleChar = firstChar.toTitleCase() + if (titleChar != firstChar.toUpperCase()) { + append(titleChar) + } else { + append(this@capitalize.substring(0, 1).toUpperCase(locale)) + } + append(this@capitalize.substring(1)) + } + } + } + return this +} + /** * Returns a copy of this string having its first letter lowercased, or the original string, * if it's empty or already starts with a lower case letter. @@ -577,6 +602,16 @@ public actual fun String.decapitalize(): String { return if (isNotEmpty() && this[0].isUpperCase()) substring(0, 1).toLowerCase() + substring(1) else this } +/** + * Returns a copy of this string having its first letter lowercased using [locale], or the original string, + * if it's empty or already starts with a lower case letter. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public fun String.decapitalize(locale: Locale): String { + return if (isNotEmpty() && this[0].isUpperCase()) substring(0, 1).toLowerCase(locale) + substring(1) else this +} + /** * Returns a string containing this char sequence repeated [n] times. * @throws [IllegalArgumentException] when n < 0. diff --git a/libraries/stdlib/jvm/test/text/StringJVMTest.kt b/libraries/stdlib/jvm/test/text/StringJVMTest.kt index 99a1ee3f013..eb16384e9b4 100644 --- a/libraries/stdlib/jvm/test/text/StringJVMTest.kt +++ b/libraries/stdlib/jvm/test/text/StringJVMTest.kt @@ -67,4 +67,32 @@ class StringJVMTest { assertEquals("UTF-32LE", Charsets.UTF_32LE.name()) assertEquals("UTF-32BE", Charsets.UTF_32BE.name()) } + + @ExperimentalStdlibApi + @Test fun capitalizeLocale() { + assertEquals("ABC", "ABC".capitalize(Locale.US)) + assertEquals("Abc", "Abc".capitalize(Locale.US)) + assertEquals("Abc", "abc".capitalize(Locale.US)) + + // Locale-specific case mappings. + assertEquals("İii", "iii".capitalize(Locale("tr", "TR"))) + assertEquals("Iii", "iii".capitalize(Locale.US)) + + // Case mapping that results in multiple characters (validating Character.toUpperCase was not used). + assertEquals("SSßß", "ßßß".capitalize(Locale.US)) + + // Case mapping where title case is different than uppercase and so Character.toTitleCase is preferred. + assertEquals("Dzdzdz", "dzdzdz".capitalize(Locale.US)) + } + + @ExperimentalStdlibApi + @Test fun decapitalizeLocale() { + assertEquals("aBC", "ABC".decapitalize(Locale.US)) + assertEquals("abc", "Abc".decapitalize(Locale.US)) + assertEquals("abc", "abc".decapitalize(Locale.US)) + + // Locale-specific case mappings. + assertEquals("ıII", "III".decapitalize(Locale("tr", "TR"))) + assertEquals("iII", "III".decapitalize(Locale.US)) + } } diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index c393b89da79..2c6635a921f 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -4896,6 +4896,7 @@ public final class kotlin/text/StringsKt { public static final fun associateWith (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateWithTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun capitalize (Ljava/lang/String;)Ljava/lang/String; + public static final fun capitalize (Ljava/lang/String;Ljava/util/Locale;)Ljava/lang/String; public static final fun chunked (Ljava/lang/CharSequence;I)Ljava/util/List; public static final fun chunked (Ljava/lang/CharSequence;ILkotlin/jvm/functions/Function1;)Ljava/util/List; public static final fun chunkedSequence (Ljava/lang/CharSequence;I)Lkotlin/sequences/Sequence; @@ -4916,6 +4917,7 @@ public final class kotlin/text/StringsKt { public static synthetic fun contains$default (Ljava/lang/CharSequence;Ljava/lang/CharSequence;ZILjava/lang/Object;)Z public static final fun count (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)I public static final fun decapitalize (Ljava/lang/String;)Ljava/lang/String; + public static final fun decapitalize (Ljava/lang/String;Ljava/util/Locale;)Ljava/lang/String; public static final fun decodeToString ([B)Ljava/lang/String; public static final fun decodeToString ([BIIZ)Ljava/lang/String; public static synthetic fun decodeToString$default ([BIIZILjava/lang/Object;)Ljava/lang/String;