diff options
author | Elliott Hughes <enh@google.com> | 2014-05-29 17:41:25 -0700 |
---|---|---|
committer | Narayan Kamath <narayan@google.com> | 2014-06-16 11:47:41 +0100 |
commit | a94266074c7b82720fd2cecfb37ab8da85f1b296 (patch) | |
tree | 3a0b903bb2bbaaeb4fb8360deeb35eccabee2e9e | |
parent | f577d7594f3acc1d97e119c508e8e4edfeb1f20b (diff) | |
download | libcore-a94266074c7b82720fd2cecfb37ab8da85f1b296.zip libcore-a94266074c7b82720fd2cecfb37ab8da85f1b296.tar.gz libcore-a94266074c7b82720fd2cecfb37ab8da85f1b296.tar.bz2 |
Pass BCP-47 tags and not Locale.toString results to ICU.
ICU can't handle the new toString forms for scripts etc.
and it's also guaranteed to deal with BCP-47 tags correctly.
Most of the changes in this patch are required to keep
backwards compatibility for getDisplayCountry string etc. in
the face of the transformations toLanguageTag performs.
A few tests were changed, but for the better.
The tagalog -> filipino charlie foxtrot will be dealt with
in a follow up change.
Co-Authored-By: Narayan Kamath <narayan@google.com>
Change-Id: Ia7f26d92a0e38c4bbb1d839c0fbd8ad16a473bf5
22 files changed, 511 insertions, 289 deletions
diff --git a/benchmarks/src/benchmarks/regression/IcuBenchmark.java b/benchmarks/src/benchmarks/regression/IcuBenchmark.java index ee8270a..2aed36b 100644 --- a/benchmarks/src/benchmarks/regression/IcuBenchmark.java +++ b/benchmarks/src/benchmarks/regression/IcuBenchmark.java @@ -26,7 +26,7 @@ import libcore.icu.ICU; public class IcuBenchmark extends SimpleBenchmark { public void time_getBestDateTimePattern(int reps) throws Exception { for (int rep = 0; rep < reps; ++rep) { - ICU.getBestDateTimePattern("dEEEMMM", "US"); + ICU.getBestDateTimePattern("dEEEMMM", new Locale("en", "US")); } } } diff --git a/benchmarks/src/benchmarks/regression/StringCaseMappingBenchmark.java b/benchmarks/src/benchmarks/regression/StringCaseMappingBenchmark.java index cde257b..ae6b6b6 100644 --- a/benchmarks/src/benchmarks/regression/StringCaseMappingBenchmark.java +++ b/benchmarks/src/benchmarks/regression/StringCaseMappingBenchmark.java @@ -106,13 +106,13 @@ public class StringCaseMappingBenchmark extends SimpleBenchmark { public void timeToUpperCase_ICU(int reps) { for (int i = 0; i < reps; ++i) { - libcore.icu.ICU.toUpperCase(s.value, Locale.US.toString()); + libcore.icu.ICU.toUpperCase(s.value, Locale.US); } } public void timeToLowerCase_ICU(int reps) { for (int i = 0; i < reps; ++i) { - libcore.icu.ICU.toLowerCase(s.value, Locale.US.toString()); + libcore.icu.ICU.toLowerCase(s.value, Locale.US); } } diff --git a/include/ScopedIcuLocale.h b/include/ScopedIcuLocale.h new file mode 100644 index 0000000..2109e03 --- /dev/null +++ b/include/ScopedIcuLocale.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +#ifndef SCOPED_ICU_LOCALE_H_included +#define SCOPED_ICU_LOCALE_H_included + +#include "JNIHelp.h" +#include "ScopedUtfChars.h" +#include "unicode/locid.h" + +class ScopedIcuLocale { + public: + ScopedIcuLocale(JNIEnv* env, jstring javaLocaleName) : mEnv(env) { + mLocale.setToBogus(); + + if (javaLocaleName == NULL) { + jniThrowNullPointerException(mEnv, "javaLocaleName == null"); + return; + } + + const ScopedUtfChars localeName(env, javaLocaleName); + if (localeName.c_str() == NULL) { + return; + } + + mLocale = Locale::createFromName(localeName.c_str()); + } + + ~ScopedIcuLocale() { + } + + bool valid() const { + return !mLocale.isBogus(); + } + + Locale& locale() { + return mLocale; + } + + private: + JNIEnv* const mEnv; + Locale mLocale; + + // Disallow copy and assignment. + ScopedIcuLocale(const ScopedIcuLocale&); + void operator=(const ScopedIcuLocale&); +}; + +#endif // SCOPED_ICU_LOCALE_H_included diff --git a/luni/src/main/java/java/lang/CaseMapper.java b/luni/src/main/java/java/lang/CaseMapper.java index 4e411d1..1da621c 100644 --- a/luni/src/main/java/java/lang/CaseMapper.java +++ b/luni/src/main/java/java/lang/CaseMapper.java @@ -49,7 +49,7 @@ class CaseMapper { // Note that Greek isn't a particularly hard case for toLowerCase, only toUpperCase. String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { - return ICU.toLowerCase(s, locale.toString()); + return ICU.toLowerCase(s, locale); } char[] newValue = null; @@ -59,7 +59,7 @@ class CaseMapper { char newCh; if (ch == LATIN_CAPITAL_I_WITH_DOT || Character.isHighSurrogate(ch)) { // Punt these hard cases. - return ICU.toLowerCase(s, locale.toString()); + return ICU.toLowerCase(s, locale); } else if (ch == GREEK_CAPITAL_SIGMA && isFinalSigma(value, offset, count, i)) { newCh = GREEK_SMALL_FINAL_SIGMA; } else { @@ -150,7 +150,7 @@ class CaseMapper { public static String toUpperCase(Locale locale, String s, char[] value, int offset, int count) { String languageCode = locale.getLanguage(); if (languageCode.equals("tr") || languageCode.equals("az") || languageCode.equals("lt")) { - return ICU.toUpperCase(s, locale.toString()); + return ICU.toUpperCase(s, locale); } if (languageCode.equals("el")) { return EL_UPPER.get().transliterate(s); @@ -161,7 +161,7 @@ class CaseMapper { for (int o = offset, end = offset + count; o < end; o++) { char ch = value[o]; if (Character.isHighSurrogate(ch)) { - return ICU.toUpperCase(s, locale.toString()); + return ICU.toUpperCase(s, locale); } int index = upperIndex(ch); if (index == -1) { diff --git a/luni/src/main/java/java/text/BreakIterator.java b/luni/src/main/java/java/text/BreakIterator.java index b14647c..81545b2 100644 --- a/luni/src/main/java/java/text/BreakIterator.java +++ b/luni/src/main/java/java/text/BreakIterator.java @@ -268,13 +268,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * characters using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. */ - public static BreakIterator getCharacterInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(where)); + public static BreakIterator getCharacterInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getCharacterInstance(locale)); } /** @@ -290,14 +286,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * line breaks using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. - * @throws NullPointerException if {@code where} is {@code null}. */ - public static BreakIterator getLineInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(where)); + public static BreakIterator getLineInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getLineInstance(locale)); } /** @@ -313,14 +304,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * sentence-breaks using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. - * @throws NullPointerException if {@code where} is {@code null}. */ - public static BreakIterator getSentenceInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(where)); + public static BreakIterator getSentenceInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getSentenceInstance(locale)); } /** @@ -336,14 +322,9 @@ public abstract class BreakIterator implements Cloneable { /** * Returns a new instance of {@code BreakIterator} to iterate over * word-breaks using the given locale. - * - * @param where - * the given locale. - * @return a new instance of {@code BreakIterator} using the given locale. - * @throws NullPointerException if {@code where} is {@code null}. */ - public static BreakIterator getWordInstance(Locale where) { - return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(where)); + public static BreakIterator getWordInstance(Locale locale) { + return new RuleBasedBreakIterator(NativeBreakIterator.getWordInstance(locale)); } /** diff --git a/luni/src/main/java/java/util/Currency.java b/luni/src/main/java/java/util/Currency.java index e8ecdde..a535bc7 100644 --- a/luni/src/main/java/java/util/Currency.java +++ b/luni/src/main/java/java/util/Currency.java @@ -35,7 +35,7 @@ public final class Currency implements Serializable { private Currency(String currencyCode) { this.currencyCode = currencyCode; - String symbol = ICU.getCurrencySymbol(Locale.US.toString(), currencyCode); + String symbol = ICU.getCurrencySymbol(Locale.US, currencyCode); if (symbol == null) { throw new IllegalArgumentException("Unsupported ISO 4217 currency code: " + currencyCode); @@ -123,7 +123,7 @@ public final class Currency implements Serializable { * @since 1.7 */ public String getDisplayName(Locale locale) { - return ICU.getCurrencyDisplayName(locale.toString(), currencyCode); + return ICU.getCurrencyDisplayName(locale, currencyCode); } /** @@ -168,7 +168,7 @@ public final class Currency implements Serializable { } // Try ICU, and fall back to the currency code if ICU has nothing. - String symbol = ICU.getCurrencySymbol(locale.toString(), currencyCode); + String symbol = ICU.getCurrencySymbol(locale, currencyCode); return symbol != null ? symbol : currencyCode; } diff --git a/luni/src/main/java/java/util/Locale.java b/luni/src/main/java/java/util/Locale.java index 5f24138..b59385b 100644 --- a/luni/src/main/java/java/util/Locale.java +++ b/luni/src/main/java/java/util/Locale.java @@ -782,7 +782,8 @@ public final class Locale implements Cloneable, Serializable { /** * Returns a locale for a given BCP-47 language tag. This method is more * lenient than {@link Builder#setLanguageTag}. For a given language tag, parsing - * will proceed upto the first malformed subtag. All subsequent tags are discarded. + * will proceed up to the first malformed subtag. All subsequent tags are discarded. + * Note that language tags use {@code -} rather than {@code _}, for example {@code en-US}. * * @throws NullPointerException if {@code languageTag} is {@code null}. * @@ -891,17 +892,14 @@ public final class Locale implements Cloneable, Serializable { if (hasValidatedFields) { Set<String> attribsCopy = new TreeSet<String>(unicodeAttributes); - Map<String, String> keywordsCopy = new TreeMap<String, String>( - unicodeKeywords); - Map<Character, String> extensionsCopy = new TreeMap<Character, String>( - extensions); + Map<String, String> keywordsCopy = new TreeMap<String, String>(unicodeKeywords); + Map<Character, String> extensionsCopy = new TreeMap<Character, String>(extensions); // We need to transform the list of attributes & keywords set on the // builder to a unicode locale extension. i.e, if we have any keywords // or attributes set, Locale#getExtension('u') should return a well // formed extension. - addUnicodeExtensionToExtensionsMap(attribsCopy, keywordsCopy, - extensionsCopy); + addUnicodeExtensionToExtensionsMap(attribsCopy, keywordsCopy, extensionsCopy); this.unicodeAttributes = Collections.unmodifiableSet(attribsCopy); this.unicodeKeywords = Collections.unmodifiableMap(keywordsCopy); @@ -1006,10 +1004,16 @@ public final class Locale implements Cloneable, Serializable { if (countryCode.isEmpty()) { return ""; } - String result = ICU.getDisplayCountryNative(getIcuLocaleId(), locale.getIcuLocaleId()); + + try { + Builder.normalizeAndValidateRegion(countryCode); + } catch (IllformedLocaleException ex) { + return countryCode; + } + + String result = ICU.getDisplayCountry(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayCountryNative(getIcuLocaleId(), - Locale.getDefault().getIcuLocaleId()); + result = ICU.getDisplayCountry(this, Locale.getDefault()); } return result; } @@ -1030,19 +1034,24 @@ public final class Locale implements Cloneable, Serializable { return ""; } - // http://b/8049507 --- frameworks/base should use fil_PH instead of tl_PH. - // Until then, we're stuck covering their tracks, making it look like they're - // using "fil" when they're not. - String localeString = toString(); - if (languageCode.equals("tl")) { - localeString = toNewString("fil", countryCode, variantCode, scriptCode, - extensions); + // Hacks for backward compatibility. + // + // Our language tag will contain "und" if the languageCode is invalid + // or missing. ICU will then return "langue indéterminée" or the equivalent + // display language for the indeterminate language code. + // + // Sigh... ugh... and what not. + try { + Builder.normalizeAndValidateLanguage(languageCode); + } catch (IllformedLocaleException ex) { + return languageCode; } - String result = ICU.getDisplayLanguageNative(localeString, locale.getIcuLocaleId()); + // TODO: We need a new hack or a complete fix for http://b/8049507 --- We would + // cover the frameworks' tracks when they were using "tl" instead of "fil". + String result = ICU.getDisplayLanguage(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayLanguageNative(localeString, - Locale.getDefault().getIcuLocaleId()); + result = ICU.getDisplayLanguage(this, Locale.getDefault()); } return result; } @@ -1127,13 +1136,27 @@ public final class Locale implements Cloneable, Serializable { * returned. */ public String getDisplayVariant(Locale locale) { - if (variantCode.length() == 0) { + if (variantCode.isEmpty()) { + return ""; + } + + try { + Builder.normalizeAndValidateVariant(variantCode); + } catch (IllformedLocaleException ilfe) { return variantCode; } - String result = ICU.getDisplayVariantNative(getIcuLocaleId(), locale.getIcuLocaleId()); + + String result = ICU.getDisplayVariant(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayVariantNative(getIcuLocaleId(), - Locale.getDefault().getIcuLocaleId()); + result = ICU.getDisplayVariant(this, Locale.getDefault()); + } + + // The "old style" locale constructors allow us to pass in variants that aren't + // valid BCP-47 variant subtags. When that happens, toLanguageTag will not emit + // them. Note that we know variantCode.length() > 0 due to the isEmpty check at + // the beginning of this function. + if (result.isEmpty()) { + return variantCode; } return result; } @@ -1144,7 +1167,7 @@ public final class Locale implements Cloneable, Serializable { * @throws MissingResourceException if there's no 3-letter country code for this locale. */ public String getISO3Country() { - String code = ICU.getISO3CountryNative(getIcuLocaleId()); + String code = ICU.getISO3Country(this); if (!countryCode.isEmpty() && code.isEmpty()) { throw new MissingResourceException("No 3-letter country code for locale: " + this, "FormatData_" + this, "ShortCountry"); } @@ -1157,7 +1180,15 @@ public final class Locale implements Cloneable, Serializable { * @throws MissingResourceException if there's no 3-letter language code for this locale. */ public String getISO3Language() { - String code = ICU.getISO3LanguageNative(getIcuLocaleId()); + // For backward compatibility, we must return "" for an empty language + // code and not "und" which is the accurate ISO-639-3 code for an + // undetermined language. + if (languageCode.isEmpty()) { + return ""; + } + + String code = ICU.getISO3Language(this); + if (!languageCode.isEmpty() && code.isEmpty()) { throw new MissingResourceException("No 3-letter language code for locale: " + this, "FormatData_" + this, "ShortLanguage"); } @@ -1233,10 +1264,9 @@ public final class Locale implements Cloneable, Serializable { return ""; } - String result = ICU.getDisplayScriptNative(getIcuLocaleId(), locale.getIcuLocaleId()); + String result = ICU.getDisplayScript(this, locale); if (result == null) { // TODO: do we need to do this, or does ICU do it for us? - result = ICU.getDisplayScriptNative(getIcuLocaleId(), - Locale.getDefault().getIcuLocaleId()); + result = ICU.getDisplayScript(this, Locale.getDefault()); } return result; @@ -1257,7 +1287,7 @@ public final class Locale implements Cloneable, Serializable { * where they will appear after a subtag whose value is {@code "lvariant"}. * * It's also important to note that the BCP-47 tag is well formed in the sense - * that it is unambiguously parsable into its specified components. We do not + * that it is unambiguously parseable into its specified components. We do not * require that any of the components are registered with the applicable registries. * For example, we do not require scripts to be a registered ISO 15924 scripts or * languages to appear in the ISO-639-2 code list. @@ -1558,8 +1588,9 @@ public final class Locale implements Cloneable, Serializable { if (locale == null) { throw new NullPointerException("locale == null"); } + String languageTag = locale.toLanguageTag(); defaultLocale = locale; - ICU.setDefaultLocale(ICU.localeIdFromLocale(locale)); + ICU.setDefaultLocale(languageTag); } /** @@ -1577,20 +1608,12 @@ public final class Locale implements Cloneable, Serializable { public final String toString() { String result = cachedToStringResult; if (result == null) { - result = cachedToStringResult = toNewString(languageCode, countryCode, - variantCode, scriptCode, extensions); + result = cachedToStringResult = toNewString(languageCode, countryCode, variantCode, + scriptCode, extensions); } return result; } - private String getIcuLocaleId() { - if (cachedIcuLocaleId == null) { - cachedIcuLocaleId = ICU.localeIdFromLocale(this); - } - - return cachedIcuLocaleId; - } - private static String toNewString(String languageCode, String countryCode, String variantCode, String scriptCode, Map<Character, String> extensions) { // The string form of a locale that only has a variant is the empty string. @@ -1605,8 +1628,7 @@ public final class Locale implements Cloneable, Serializable { StringBuilder result = new StringBuilder(11); result.append(languageCode); - final boolean hasScriptOrExtensions = !scriptCode.isEmpty() || - !extensions.isEmpty(); + final boolean hasScriptOrExtensions = !scriptCode.isEmpty() || !extensions.isEmpty(); if (!countryCode.isEmpty() || !variantCode.isEmpty() || hasScriptOrExtensions) { result.append('_'); @@ -1812,8 +1834,7 @@ public final class Locale implements Cloneable, Serializable { return true; } - private static boolean isValidBcp47Alpha(String string, - int lowerBound, int upperBound) { + private static boolean isValidBcp47Alpha(String string, int lowerBound, int upperBound) { final int length = string.length(); if (length < lowerBound || length > upperBound) { return false; diff --git a/luni/src/main/java/libcore/icu/ICU.java b/luni/src/main/java/libcore/icu/ICU.java index cf04ff9..407ebfd 100644 --- a/luni/src/main/java/libcore/icu/ICU.java +++ b/luni/src/main/java/libcore/icu/ICU.java @@ -239,70 +239,6 @@ public final class ICU { true /* has validated fields */); } - /** - * Builds an ICU locale ID from the given locale. The format is very - * straightforward. It is a series of subtags in BCP 47 order - * {@code lang[_script][_country][_variant]} followed by the keyword - * separator {@code @} followed by a list of keywords. Each keyword is - * a key value pair, and appear in the form {@code k1=v1;k2=v2;...}. - * - * In this use case, each key is an extension identifier, and each value - * is the value of the extension. - */ - public static String localeIdFromLocale(Locale l) { - StringBuilder b = new StringBuilder(16); - b.append(l.getLanguage()); - - final boolean hasScript = !l.getScript().isEmpty(); - final boolean hasCountry = !l.getCountry().isEmpty(); - final boolean hasVariant = !l.getVariant().isEmpty(); - - if (hasScript || hasCountry || hasVariant) { - b.append('_'); - if (hasScript) { - b.append(l.getScript()); - if (hasCountry || hasVariant) { - b.append('_'); - } - } - - if (hasCountry) { - b.append(l.getCountry()); - if (hasVariant) { - b.append('_'); - } - } - - b.append(l.getVariant()); - } - - if (!l.getExtensionKeys().isEmpty()) { - b.append('@'); - // The private use extension ('x') must show up last in the list - // so we cache its value here and append it right at the end. - String privateUseExtensionValue = null; - for (char c : l.getExtensionKeys()) { - if (c == Locale.PRIVATE_USE_EXTENSION) { - privateUseExtensionValue = l.getExtension(Locale.PRIVATE_USE_EXTENSION); - } else { - b.append(c); - b.append('='); - b.append(l.getExtension(c)); - b.append(';'); - } - } - - if (privateUseExtensionValue != null) { - b.append(Locale.PRIVATE_USE_EXTENSION); - b.append('='); - b.append(privateUseExtensionValue); - b.append(';'); - } - } - - return b.toString(); - } - public static Locale[] localesFromStrings(String[] localeNames) { // We need to remove duplicates caused by the conversion of "he" to "iw", et cetera. // Java needs the obsolete code, ICU needs the modern code, but we let ICU know about @@ -349,19 +285,20 @@ public final class ICU { return localesFromStrings(getAvailableNumberFormatLocalesNative()); } - public static String getBestDateTimePattern(String skeleton, String localeName) { - String key = skeleton + "\t" + localeName; + public static String getBestDateTimePattern(String skeleton, Locale locale) { + String languageTag = locale.toLanguageTag(); + String key = skeleton + "\t" + languageTag; synchronized (CACHED_PATTERNS) { String pattern = CACHED_PATTERNS.get(key); if (pattern == null) { - pattern = getBestDateTimePatternNative(skeleton, localeName); + pattern = getBestDateTimePatternNative(skeleton, languageTag); CACHED_PATTERNS.put(key, pattern); } return pattern; } } - private static native String getBestDateTimePatternNative(String skeleton, String localeName); + private static native String getBestDateTimePatternNative(String skeleton, String languageTag); public static char[] getDateFormatOrder(String pattern) { char[] result = new char[3]; @@ -421,8 +358,17 @@ public final class ICU { // --- Case mapping. - public static native String toLowerCase(String s, String localeName); - public static native String toUpperCase(String s, String localeName); + public static String toLowerCase(String s, Locale locale) { + return toLowerCase(s, locale.toLanguageTag()); + } + + private static native String toLowerCase(String s, String languageTag); + + public static String toUpperCase(String s, Locale locale) { + return toUpperCase(s, locale.toLanguageTag()); + } + + private static native String toUpperCase(String s, String languageTag); // --- Errors. @@ -448,18 +394,57 @@ public final class ICU { public static native String[] getAvailableCurrencyCodes(); public static native String getCurrencyCode(String countryCode); - public static native String getCurrencyDisplayName(String locale, String currencyCode); + + public static String getCurrencyDisplayName(Locale locale, String currencyCode) { + return getCurrencyDisplayName(locale.toLanguageTag(), currencyCode); + } + + private static native String getCurrencyDisplayName(String languageTag, String currencyCode); + public static native int getCurrencyFractionDigits(String currencyCode); public static native int getCurrencyNumericCode(String currencyCode); - public static native String getCurrencySymbol(String locale, String currencyCode); - public static native String getDisplayCountryNative(String countryCode, String locale); - public static native String getDisplayLanguageNative(String languageCode, String locale); - public static native String getDisplayVariantNative(String variantCode, String locale); - public static native String getDisplayScriptNative(String variantCode, String locale); + public static String getCurrencySymbol(Locale locale, String currencyCode) { + return getCurrencySymbol(locale.toLanguageTag(), currencyCode); + } + + private static native String getCurrencySymbol(String languageTag, String currencyCode); + + public static String getDisplayCountry(Locale targetLocale, Locale locale) { + return getDisplayCountryNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } + + private static native String getDisplayCountryNative(String targetLanguageTag, String languageTag); + + public static String getDisplayLanguage(Locale targetLocale, Locale locale) { + return getDisplayLanguageNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } + + private static native String getDisplayLanguageNative(String targetLanguageTag, String languageTag); + + public static String getDisplayVariant(Locale targetLocale, Locale locale) { + return getDisplayVariantNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } + + private static native String getDisplayVariantNative(String targetLanguageTag, String languageTag); + + public static String getDisplayScript(Locale targetLocale, Locale locale) { + return getDisplayScriptNative(targetLocale.toLanguageTag(), locale.toLanguageTag()); + } + + private static native String getDisplayScriptNative(String targetLanguageTag, String languageTag); + + public static String getISO3Country(Locale locale) { + return getISO3CountryNative(locale.toLanguageTag()); + } + + private static native String getISO3CountryNative(String languageTag); + + public static String getISO3Language(Locale locale) { + return getISO3LanguageNative(locale.toLanguageTag()); + } - public static native String getISO3CountryNative(String locale); - public static native String getISO3LanguageNative(String locale); + private static native String getISO3LanguageNative(String languageTag); public static native String addLikelySubtags(String locale); public static native String getScript(String locale); @@ -471,6 +456,6 @@ public final class ICU { static native boolean initLocaleDataNative(String locale, LocaleData result); - public static native void setDefaultLocale(String locale); + public static native void setDefaultLocale(String languageTag); public static native String getDefaultLocale(); } diff --git a/luni/src/main/java/libcore/icu/LocaleData.java b/luni/src/main/java/libcore/icu/LocaleData.java index 49228b3..845ba32 100644 --- a/luni/src/main/java/libcore/icu/LocaleData.java +++ b/luni/src/main/java/libcore/icu/LocaleData.java @@ -176,8 +176,8 @@ public final class LocaleData { } // Get the "h:mm a" and "HH:mm" 12- and 24-hour time format strings. - localeData.timeFormat12 = ICU.getBestDateTimePattern("hm", locale.toString()); - localeData.timeFormat24 = ICU.getBestDateTimePattern("Hm", locale.toString()); + localeData.timeFormat12 = ICU.getBestDateTimePattern("hm", locale); + localeData.timeFormat24 = ICU.getBestDateTimePattern("Hm", locale); // Fix up a couple of patterns. if (localeData.fullTimeFormat != null) { diff --git a/luni/src/main/java/libcore/icu/NativeBreakIterator.java b/luni/src/main/java/libcore/icu/NativeBreakIterator.java index 7168d96..992aac2 100644 --- a/luni/src/main/java/libcore/icu/NativeBreakIterator.java +++ b/luni/src/main/java/libcore/icu/NativeBreakIterator.java @@ -138,23 +138,23 @@ public final class NativeBreakIterator implements Cloneable { } public int preceding(int offset) { - return precedingImpl(this.address, this.string, offset); + return precedingImpl(this.address, this.string, offset); } - public static NativeBreakIterator getCharacterInstance(Locale where) { - return new NativeBreakIterator(getCharacterInstanceImpl(where.toString()), BI_CHAR_INSTANCE); + public static NativeBreakIterator getCharacterInstance(Locale locale) { + return new NativeBreakIterator(getCharacterInstanceImpl(locale.toLanguageTag()), BI_CHAR_INSTANCE); } - public static NativeBreakIterator getLineInstance(Locale where) { - return new NativeBreakIterator(getLineInstanceImpl(where.toString()), BI_LINE_INSTANCE); + public static NativeBreakIterator getLineInstance(Locale locale) { + return new NativeBreakIterator(getLineInstanceImpl(locale.toLanguageTag()), BI_LINE_INSTANCE); } - public static NativeBreakIterator getSentenceInstance(Locale where) { - return new NativeBreakIterator(getSentenceInstanceImpl(where.toString()), BI_SENT_INSTANCE); + public static NativeBreakIterator getSentenceInstance(Locale locale) { + return new NativeBreakIterator(getSentenceInstanceImpl(locale.toLanguageTag()), BI_SENT_INSTANCE); } - public static NativeBreakIterator getWordInstance(Locale where) { - return new NativeBreakIterator(getWordInstanceImpl(where.toString()), BI_WORD_INSTANCE); + public static NativeBreakIterator getWordInstance(Locale locale) { + return new NativeBreakIterator(getWordInstanceImpl(locale.toLanguageTag()), BI_WORD_INSTANCE); } private static native long getCharacterInstanceImpl(String locale); diff --git a/luni/src/main/java/libcore/icu/NativeCollation.java b/luni/src/main/java/libcore/icu/NativeCollation.java index 64e0278..b4b4f46 100644 --- a/luni/src/main/java/libcore/icu/NativeCollation.java +++ b/luni/src/main/java/libcore/icu/NativeCollation.java @@ -10,6 +10,8 @@ package libcore.icu; +import java.util.Locale; + /** * Package static class for declaring all native methods for collation use. * @author syn wee quek @@ -26,7 +28,10 @@ public final class NativeCollation { public static native long getCollationElementIterator(long address, String source); public static native String getRules(long address); public static native byte[] getSortKey(long address, String source); - public static native long openCollator(String locale); + public static long openCollator(Locale locale) { + return openCollator(locale.toLanguageTag()); + } + private static native long openCollator(String languageTag); public static native long openCollatorFromRules(String rules, int normalizationMode, int collationStrength); public static native long safeClone(long address); public static native void setAttribute(long address, int type, int value); diff --git a/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java b/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java index 3ea942d..b23013b 100644 --- a/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java +++ b/luni/src/main/java/libcore/icu/RuleBasedCollatorICU.java @@ -52,7 +52,7 @@ public final class RuleBasedCollatorICU implements Cloneable { } public RuleBasedCollatorICU(Locale locale) { - address = NativeCollation.openCollator(locale.toString()); + address = NativeCollation.openCollator(locale); } private RuleBasedCollatorICU(long address) { diff --git a/luni/src/main/native/IcuUtilities.cpp b/luni/src/main/native/IcuUtilities.cpp index c6f3950..7ce2168 100644 --- a/luni/src/main/native/IcuUtilities.cpp +++ b/luni/src/main/native/IcuUtilities.cpp @@ -28,10 +28,6 @@ #include "unicode/uloc.h" #include "unicode/ustring.h" -Locale getLocale(JNIEnv* env, jstring localeName) { - return Locale::createFromName(ScopedUtfChars(env, localeName).c_str()); -} - jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* provider, StringEnumeration* se) { if (maybeThrowIcuException(env, provider, status)) { return NULL; diff --git a/luni/src/main/native/IcuUtilities.h b/luni/src/main/native/IcuUtilities.h index ffcfcda..737379e 100644 --- a/luni/src/main/native/IcuUtilities.h +++ b/luni/src/main/native/IcuUtilities.h @@ -23,9 +23,7 @@ #include "jni.h" #include "ustrenum.h" // For UStringEnumeration. #include "unicode/utypes.h" // For UErrorCode. -#include "unicode/locid.h" // For Locale. -extern Locale getLocale(JNIEnv* env, jstring localeName); extern jobjectArray fromStringEnumeration(JNIEnv* env, UErrorCode& status, const char* provider, StringEnumeration*); bool maybeThrowIcuException(JNIEnv* env, const char* function, UErrorCode error); diff --git a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp index bb05193..e0638bd 100644 --- a/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp +++ b/luni/src/main/native/libcore_icu_AlphabeticIndex.cpp @@ -20,6 +20,7 @@ #include "JNIHelp.h" #include "JniConstants.h" #include "JniException.h" +#include "ScopedIcuLocale.h" #include "ScopedJavaUnicodeString.h" #include "unicode/alphaindex.h" #include "unicode/uniset.h" @@ -28,9 +29,13 @@ static AlphabeticIndex* fromPeer(jlong peer) { return reinterpret_cast<AlphabeticIndex*>(static_cast<uintptr_t>(peer)); } -static jlong AlphabeticIndex_create(JNIEnv* env, jclass, jstring javaLocale) { +static jlong AlphabeticIndex_create(JNIEnv* env, jclass, jstring javaLocaleName) { UErrorCode status = U_ZERO_ERROR; - AlphabeticIndex* ai = new AlphabeticIndex(getLocale(env, javaLocale), status); + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return 0; + } + AlphabeticIndex* ai = new AlphabeticIndex(icuLocale.locale(), status); if (maybeThrowIcuException(env, "AlphabeticIndex", status)) { return 0; } @@ -53,10 +58,14 @@ static void AlphabeticIndex_setMaxLabelCount(JNIEnv* env, jclass, jlong peer, ji maybeThrowIcuException(env, "AlphabeticIndex::setMaxLabelCount", status); } -static void AlphabeticIndex_addLabels(JNIEnv* env, jclass, jlong peer, jstring javaLocale) { +static void AlphabeticIndex_addLabels(JNIEnv* env, jclass, jlong peer, jstring javaLocaleName) { AlphabeticIndex* ai = fromPeer(peer); + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return; + } UErrorCode status = U_ZERO_ERROR; - ai->addLabels(getLocale(env, javaLocale), status); + ai->addLabels(icuLocale.locale(), status); maybeThrowIcuException(env, "AlphabeticIndex::addLabels", status); } diff --git a/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp b/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp index 72bc631..a3258c1 100644 --- a/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp +++ b/luni/src/main/native/libcore_icu_DateIntervalFormat.cpp @@ -18,13 +18,17 @@ #include "IcuUtilities.h" #include "JniConstants.h" +#include "ScopedIcuLocale.h" #include "ScopedJavaUnicodeString.h" #include "UniquePtr.h" #include "cutils/log.h" #include "unicode/dtitvfmt.h" static jlong DateIntervalFormat_createDateIntervalFormat(JNIEnv* env, jclass, jstring javaSkeleton, jstring javaLocaleName, jstring javaTzName) { - Locale locale = getLocale(env, javaLocaleName); + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return 0; + } ScopedJavaUnicodeString skeletonHolder(env, javaSkeleton); if (!skeletonHolder.valid()) { @@ -32,7 +36,7 @@ static jlong DateIntervalFormat_createDateIntervalFormat(JNIEnv* env, jclass, js } UErrorCode status = U_ZERO_ERROR; - DateIntervalFormat* formatter(DateIntervalFormat::createInstance(skeletonHolder.unicodeString(), locale, status)); + DateIntervalFormat* formatter(DateIntervalFormat::createInstance(skeletonHolder.unicodeString(), icuLocale.locale(), status)); if (maybeThrowIcuException(env, "DateIntervalFormat::createInstance", status)) { return 0; } diff --git a/luni/src/main/native/libcore_icu_ICU.cpp b/luni/src/main/native/libcore_icu_ICU.cpp index 7b1aac1..8cf58bc 100644 --- a/luni/src/main/native/libcore_icu_ICU.cpp +++ b/luni/src/main/native/libcore_icu_ICU.cpp @@ -21,6 +21,7 @@ #include "JniConstants.h" #include "JniException.h" #include "ScopedFd.h" +#include "ScopedIcuLocale.h" #include "ScopedJavaUnicodeString.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" @@ -96,30 +97,30 @@ class ScopedResourceBundle { DISALLOW_COPY_AND_ASSIGN(ScopedResourceBundle); }; -static jstring ICU_addLikelySubtags(JNIEnv* env, jclass, jstring javaLocale) { +static jstring ICU_addLikelySubtags(JNIEnv* env, jclass, jstring javaLocaleName) { UErrorCode status = U_ZERO_ERROR; - ScopedUtfChars localeID(env, javaLocale); + ScopedUtfChars localeID(env, javaLocaleName); char maximizedLocaleID[ULOC_FULLNAME_CAPACITY]; uloc_addLikelySubtags(localeID.c_str(), maximizedLocaleID, sizeof(maximizedLocaleID), &status); if (U_FAILURE(status)) { - return javaLocale; + return javaLocaleName; } return env->NewStringUTF(maximizedLocaleID); } -static jstring ICU_getScript(JNIEnv* env, jclass, jstring javaLocale) { - UErrorCode status = U_ZERO_ERROR; - ScopedUtfChars localeID(env, javaLocale); - char script[ULOC_SCRIPT_CAPACITY]; - uloc_getScript(localeID.c_str(), script, sizeof(script), &status); - if (U_FAILURE(status)) { - return NULL; - } - return env->NewStringUTF(script); +static jstring ICU_getScript(JNIEnv* env, jclass, jstring javaLocaleName) { + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return NULL; + } + return env->NewStringUTF(icuLocale.locale().getScript()); } static jstring ICU_localeForLanguageTag(JNIEnv* env, jclass, jstring languageTag, jboolean strict) { ScopedUtfChars languageTagChars(env, languageTag); + if (languageTagChars.c_str() == NULL) { + return NULL; + } // Naively assume that in the average case, the size of // the normalized language tag will be very nearly the same as the @@ -158,8 +159,7 @@ static jstring ICU_localeForLanguageTag(JNIEnv* env, jclass, jstring languageTag // NOTE: The cast is safe because parsedLength can never be negative thanks // to the check above. ICU does not document any negative return values for // that field, but check for it anyway. - if ((strict == JNI_TRUE) && - (static_cast<uint32_t>(parsedLength) != languageTagChars.size())) { + if ((strict == JNI_TRUE) && (static_cast<uint32_t>(parsedLength) != languageTagChars.size())) { return NULL; } @@ -228,9 +228,9 @@ static jstring ICU_getCurrencyCode(JNIEnv* env, jclass, jstring javaCountryCode) return (charCount == 0) ? env->NewStringUTF("XXX") : env->NewString(chars, charCount); } -static jstring getCurrencyName(JNIEnv* env, jstring javaLocaleName, jstring javaCurrencyCode, UCurrNameStyle nameStyle) { - ScopedUtfChars localeName(env, javaLocaleName); - if (localeName.c_str() == NULL) { +static jstring getCurrencyName(JNIEnv* env, jstring javaLanguageTag, jstring javaCurrencyCode, UCurrNameStyle nameStyle) { + ScopedUtfChars languageTag(env, javaLanguageTag); + if (languageTag.c_str() == NULL) { return NULL; } ScopedJavaUnicodeString currencyCode(env, javaCurrencyCode); @@ -241,7 +241,7 @@ static jstring getCurrencyName(JNIEnv* env, jstring javaLocaleName, jstring java UErrorCode status = U_ZERO_ERROR; UBool isChoiceFormat = false; int32_t charCount; - const UChar* chars = ucurr_getName(icuCurrencyCode.getTerminatedBuffer(), localeName.c_str(), + const UChar* chars = ucurr_getName(icuCurrencyCode.getTerminatedBuffer(), languageTag.c_str(), nameStyle, &isChoiceFormat, &charCount, &status); if (status == U_USING_DEFAULT_WARNING) { if (nameStyle == UCURR_SYMBOL_NAME) { @@ -260,54 +260,88 @@ static jstring getCurrencyName(JNIEnv* env, jstring javaLocaleName, jstring java return (charCount == 0) ? NULL : env->NewString(chars, charCount); } -static jstring ICU_getCurrencyDisplayName(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaCurrencyCode) { - return getCurrencyName(env, javaLocaleName, javaCurrencyCode, UCURR_LONG_NAME); +static jstring ICU_getCurrencyDisplayName(JNIEnv* env, jclass, jstring javaLanguageTag, jstring javaCurrencyCode) { + return getCurrencyName(env, javaLanguageTag, javaCurrencyCode, UCURR_LONG_NAME); } -static jstring ICU_getCurrencySymbol(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaCurrencyCode) { - return getCurrencyName(env, javaLocaleName, javaCurrencyCode, UCURR_SYMBOL_NAME); +static jstring ICU_getCurrencySymbol(JNIEnv* env, jclass, jstring javaLanguageTag, jstring javaCurrencyCode) { + return getCurrencyName(env, javaLanguageTag, javaCurrencyCode, UCURR_SYMBOL_NAME); } -static jstring ICU_getDisplayCountryNative(JNIEnv* env, jclass, jstring targetLocale, jstring locale) { - Locale loc = getLocale(env, locale); - Locale targetLoc = getLocale(env, targetLocale); - UnicodeString str; - targetLoc.getDisplayCountry(loc, str); - return env->NewString(str.getBuffer(), str.length()); +static jstring ICU_getDisplayCountryNative(JNIEnv* env, jclass, jstring javaTargetLanguageTag, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + ScopedIcuLocale icuTargetLocale(env, javaTargetLanguageTag); + if (!icuTargetLocale.valid()) { + return NULL; + } + + UnicodeString str; + icuTargetLocale.locale().getDisplayCountry(icuLocale.locale(), str); + return env->NewString(str.getBuffer(), str.length()); } -static jstring ICU_getDisplayLanguageNative(JNIEnv* env, jclass, jstring targetLocale, jstring locale) { - Locale loc = getLocale(env, locale); - Locale targetLoc = getLocale(env, targetLocale); - UnicodeString str; - targetLoc.getDisplayLanguage(loc, str); - return env->NewString(str.getBuffer(), str.length()); +static jstring ICU_getDisplayLanguageNative(JNIEnv* env, jclass, jstring javaTargetLanguageTag, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + ScopedIcuLocale icuTargetLocale(env, javaTargetLanguageTag); + if (!icuTargetLocale.valid()) { + return NULL; + } + + UnicodeString str; + icuTargetLocale.locale().getDisplayLanguage(icuLocale.locale(), str); + return env->NewString(str.getBuffer(), str.length()); } -static jstring ICU_getDisplayScriptNative(JNIEnv* env, jclass, jstring targetLocale, jstring locale) { - Locale loc = getLocale(env, locale); - Locale targetLoc = getLocale(env, targetLocale); - UnicodeString str; - targetLoc.getDisplayScript(loc, str); - return env->NewString(str.getBuffer(), str.length()); +static jstring ICU_getDisplayScriptNative(JNIEnv* env, jclass, jstring javaTargetLanguageTag, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + ScopedIcuLocale icuTargetLocale(env, javaTargetLanguageTag); + if (!icuTargetLocale.valid()) { + return NULL; + } + + UnicodeString str; + icuTargetLocale.locale().getDisplayScript(icuLocale.locale(), str); + return env->NewString(str.getBuffer(), str.length()); } -static jstring ICU_getDisplayVariantNative(JNIEnv* env, jclass, jstring targetLocale, jstring locale) { - Locale loc = getLocale(env, locale); - Locale targetLoc = getLocale(env, targetLocale); - UnicodeString str; - targetLoc.getDisplayVariant(loc, str); - return env->NewString(str.getBuffer(), str.length()); +static jstring ICU_getDisplayVariantNative(JNIEnv* env, jclass, jstring javaTargetLanguageTag, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + ScopedIcuLocale icuTargetLocale(env, javaTargetLanguageTag); + if (!icuTargetLocale.valid()) { + return NULL; + } + + UnicodeString str; + icuTargetLocale.locale().getDisplayVariant(icuLocale.locale(), str); + return env->NewString(str.getBuffer(), str.length()); } -static jstring ICU_getISO3CountryNative(JNIEnv* env, jclass, jstring locale) { - Locale loc = getLocale(env, locale); - return env->NewStringUTF(loc.getISO3Country()); +static jstring ICU_getISO3CountryNative(JNIEnv* env, jclass, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + return env->NewStringUTF(icuLocale.locale().getISO3Country()); } -static jstring ICU_getISO3LanguageNative(JNIEnv* env, jclass, jstring locale) { - Locale loc = getLocale(env, locale); - return env->NewStringUTF(loc.getISO3Language()); +static jstring ICU_getISO3LanguageNative(JNIEnv* env, jclass, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + return env->NewStringUTF(icuLocale.locale().getISO3Language()); } static jobjectArray ICU_getISOCountriesNative(JNIEnv* env, jclass) { @@ -538,7 +572,10 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLocale return JNI_FALSE; // ICU has a fixed-length limit. } - Locale locale = getLocale(env, javaLocaleName); + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return JNI_FALSE; + } // Get the DateTimePatterns. UErrorCode status = U_ZERO_ERROR; @@ -557,7 +594,7 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLocale // Get the "Yesterday", "Today", and "Tomorrow" strings. bool foundYesterdayTodayAndTomorrow = false; for (LocaleNameIterator it(localeName.c_str(), status); it.HasNext(); it.Up()) { - if (getYesterdayTodayAndTomorrow(env, localeData, locale, it.Get())) { + if (getYesterdayTodayAndTomorrow(env, localeData, icuLocale.locale(), it.Get())) { foundYesterdayTodayAndTomorrow = true; break; } @@ -568,7 +605,7 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLocale } status = U_ZERO_ERROR; - UniquePtr<Calendar> cal(Calendar::createInstance(locale, status)); + UniquePtr<Calendar> cal(Calendar::createInstance(icuLocale.locale(), status)); if (U_FAILURE(status)) { return JNI_FALSE; } @@ -578,7 +615,7 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLocale // Get DateFormatSymbols. status = U_ZERO_ERROR; - DateFormatSymbols dateFormatSym(locale, status); + DateFormatSymbols dateFormatSym(icuLocale.locale(), status); if (U_FAILURE(status)) { return JNI_FALSE; } @@ -631,8 +668,8 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLocale status = U_ZERO_ERROR; // For numberPatterns and symbols. - setNumberPatterns(env, localeData, locale); - setDecimalFormatSymbolsData(env, localeData, locale); + setNumberPatterns(env, localeData, icuLocale.locale()); + setDecimalFormatSymbolsData(env, localeData, icuLocale.locale()); jstring countryCode = env->NewStringUTF(Locale::createFromName(localeName.c_str()).getCountry()); jstring internationalCurrencySymbol = ICU_getCurrencyCode(env, NULL, countryCode); @@ -655,25 +692,33 @@ static jboolean ICU_initLocaleDataNative(JNIEnv* env, jclass, jstring javaLocale return JNI_TRUE; } -static jstring ICU_toLowerCase(JNIEnv* env, jclass, jstring javaString, jstring localeName) { +static jstring ICU_toLowerCase(JNIEnv* env, jclass, jstring javaString, jstring javaLanguageTag) { ScopedJavaUnicodeString scopedString(env, javaString); if (!scopedString.valid()) { return NULL; } + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } UnicodeString& s(scopedString.unicodeString()); UnicodeString original(s); - s.toLower(getLocale(env, localeName)); + s.toLower(icuLocale.locale()); return s == original ? javaString : env->NewString(s.getBuffer(), s.length()); } -static jstring ICU_toUpperCase(JNIEnv* env, jclass, jstring javaString, jstring localeName) { +static jstring ICU_toUpperCase(JNIEnv* env, jclass, jstring javaString, jstring javaLanguageTag) { ScopedJavaUnicodeString scopedString(env, javaString); if (!scopedString.valid()) { return NULL; } + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } UnicodeString& s(scopedString.unicodeString()); UnicodeString original(s); - s.toUpper(getLocale(env, localeName)); + s.toUpper(icuLocale.locale()); return s == original ? javaString : env->NewString(s.getBuffer(), s.length()); } @@ -708,10 +753,14 @@ static jobject ICU_getAvailableCurrencyCodes(JNIEnv* env, jclass) { return fromStringEnumeration(env, status, "ucurr_openISOCurrencies", &e); } -static jstring ICU_getBestDateTimePatternNative(JNIEnv* env, jclass, jstring javaSkeleton, jstring javaLocaleName) { - Locale locale = getLocale(env, javaLocaleName); +static jstring ICU_getBestDateTimePatternNative(JNIEnv* env, jclass, jstring javaSkeleton, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return NULL; + } + UErrorCode status = U_ZERO_ERROR; - UniquePtr<DateTimePatternGenerator> generator(DateTimePatternGenerator::createInstance(locale, status)); + UniquePtr<DateTimePatternGenerator> generator(DateTimePatternGenerator::createInstance(icuLocale.locale(), status)); if (maybeThrowIcuException(env, "DateTimePatternGenerator::createInstance", status)) { return NULL; } @@ -728,16 +777,14 @@ static jstring ICU_getBestDateTimePatternNative(JNIEnv* env, jclass, jstring jav return env->NewString(result.getBuffer(), result.length()); } -static void ICU_setDefaultLocale(JNIEnv* env, jclass, jstring javaLocaleName) { - Locale locale = getLocale(env, javaLocaleName); - UErrorCode status = U_ZERO_ERROR; +static void ICU_setDefaultLocale(JNIEnv* env, jclass, jstring javaLanguageTag) { + ScopedIcuLocale icuLocale(env, javaLanguageTag); + if (!icuLocale.valid()) { + return; + } - // TODO: Should we check whether locale.isBogus() here ? ICU will - // accept bogus locales as the default without complaint. It - // shouldn't make a difference in practice, users that set a bogus - // locale as the default shouldn't have any realistic expectation that - // things like defaults etc. will work correctly. - Locale::setDefault(locale, status); + UErrorCode status = U_ZERO_ERROR; + Locale::setDefault(icuLocale.locale(), status); maybeThrowIcuException(env, "Locale::setDefault", status); } diff --git a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp index 0c8c3c9..ef0c2a9 100644 --- a/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp +++ b/luni/src/main/native/libcore_icu_NativeBreakIterator.cpp @@ -20,6 +20,7 @@ #include "JNIHelp.h" #include "JniConstants.h" #include "JniException.h" +#include "ScopedIcuLocale.h" #include "ScopedUtfChars.h" #include "unicode/brkiter.h" #include "unicode/putil.h" @@ -107,13 +108,12 @@ class BreakIteratorAccessor { }; #define MAKE_BREAK_ITERATOR_INSTANCE(F) \ - UErrorCode status = U_ZERO_ERROR; \ - const ScopedUtfChars localeChars(env, javaLocale); \ - if (localeChars.c_str() == NULL) { \ + ScopedIcuLocale icuLocale(env, javaLocaleName); \ + if (!icuLocale.valid()) { \ return 0; \ } \ - Locale locale(Locale::createFromName(localeChars.c_str())); \ - BreakIterator* it = F(locale, status); \ + UErrorCode status = U_ZERO_ERROR; \ + BreakIterator* it = F(icuLocale.locale(), status); \ if (maybeThrowIcuException(env, "ubrk_open", status)) { \ return 0; \ } \ @@ -143,19 +143,19 @@ static jint NativeBreakIterator_followingImpl(JNIEnv* env, jclass, jlong address return it->following(offset); } -static jlong NativeBreakIterator_getCharacterInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) { +static jlong NativeBreakIterator_getCharacterInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createCharacterInstance); } -static jlong NativeBreakIterator_getLineInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) { +static jlong NativeBreakIterator_getLineInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createLineInstance); } -static jlong NativeBreakIterator_getSentenceInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) { +static jlong NativeBreakIterator_getSentenceInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createSentenceInstance); } -static jlong NativeBreakIterator_getWordInstanceImpl(JNIEnv* env, jclass, jstring javaLocale) { +static jlong NativeBreakIterator_getWordInstanceImpl(JNIEnv* env, jclass, jstring javaLocaleName) { MAKE_BREAK_ITERATOR_INSTANCE(BreakIterator::createWordInstance); } diff --git a/luni/src/main/native/libcore_icu_NativeCollation.cpp b/luni/src/main/native/libcore_icu_NativeCollation.cpp index 00ec9ae..4ce42ec 100644 --- a/luni/src/main/native/libcore_icu_NativeCollation.cpp +++ b/luni/src/main/native/libcore_icu_NativeCollation.cpp @@ -180,11 +180,12 @@ static jint NativeCollation_next(JNIEnv* env, jclass, jlong address) { return result; } -static jlong NativeCollation_openCollator(JNIEnv* env, jclass, jstring localeName) { - ScopedUtfChars localeChars(env, localeName); +static jlong NativeCollation_openCollator(JNIEnv* env, jclass, jstring javaLocaleName) { + ScopedUtfChars localeChars(env, javaLocaleName); if (localeChars.c_str() == NULL) { return 0; } + UErrorCode status = U_ZERO_ERROR; UCollator* c = ucol_open(localeChars.c_str(), &status); maybeThrowIcuException(env, "ucol_open", status); diff --git a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp index ef1743e..faf87f1 100644 --- a/luni/src/main/native/libcore_icu_TimeZoneNames.cpp +++ b/luni/src/main/native/libcore_icu_TimeZoneNames.cpp @@ -20,6 +20,7 @@ #include "JNIHelp.h" #include "JniConstants.h" #include "JniException.h" +#include "ScopedIcuLocale.h" #include "ScopedJavaUnicodeString.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" @@ -59,11 +60,14 @@ static bool setStringArrayElement(JNIEnv* env, jobjectArray array, int i, const return true; } -static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring localeName, jobjectArray result) { - Locale locale = getLocale(env, localeName); +static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring javaLocaleName, jobjectArray result) { + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return; + } UErrorCode status = U_ZERO_ERROR; - UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(locale, status)); + UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(icuLocale.locale(), status)); if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) { return; } @@ -118,11 +122,14 @@ static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring localeNam } } -static jstring TimeZoneNames_getExemplarLocation(JNIEnv* env, jclass, jstring javaLocale, jstring javaTz) { - Locale locale = getLocale(env, javaLocale); +static jstring TimeZoneNames_getExemplarLocation(JNIEnv* env, jclass, jstring javaLocaleName, jstring javaTz) { + ScopedIcuLocale icuLocale(env, javaLocaleName); + if (!icuLocale.valid()) { + return NULL; + } UErrorCode status = U_ZERO_ERROR; - UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(locale, status)); + UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(icuLocale.locale(), status)); if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) { return NULL; } diff --git a/luni/src/test/java/libcore/icu/ICUTest.java b/luni/src/test/java/libcore/icu/ICUTest.java index 60ed0ca..be2da25 100644 --- a/luni/src/test/java/libcore/icu/ICUTest.java +++ b/luni/src/test/java/libcore/icu/ICUTest.java @@ -16,6 +16,8 @@ package libcore.icu; +import java.text.BreakIterator; +import java.text.Collator; import java.util.Arrays; import java.util.Locale; @@ -42,12 +44,12 @@ public class ICUTest extends junit.framework.TestCase { } public void test_getBestDateTimePattern() throws Exception { - assertEquals("d MMMM", ICU.getBestDateTimePattern("MMMMd", "ca_ES")); - assertEquals("d 'de' MMMM", ICU.getBestDateTimePattern("MMMMd", "es_ES")); - assertEquals("d. MMMM", ICU.getBestDateTimePattern("MMMMd", "de_CH")); - assertEquals("MMMM d", ICU.getBestDateTimePattern("MMMMd", "en_US")); - assertEquals("d LLLL", ICU.getBestDateTimePattern("MMMMd", "fa_IR")); - assertEquals("M月d日", ICU.getBestDateTimePattern("MMMMd", "ja_JP")); + assertEquals("d MMMM", ICU.getBestDateTimePattern("MMMMd", new Locale("ca", "ES"))); + assertEquals("d 'de' MMMM", ICU.getBestDateTimePattern("MMMMd", new Locale("es", "ES"))); + assertEquals("d. MMMM", ICU.getBestDateTimePattern("MMMMd", new Locale("de", "CH"))); + assertEquals("MMMM d", ICU.getBestDateTimePattern("MMMMd", new Locale("en", "US"))); + assertEquals("d LLLL", ICU.getBestDateTimePattern("MMMMd", new Locale("fa", "IR"))); + assertEquals("M月d日", ICU.getBestDateTimePattern("MMMMd", new Locale("ja", "JP"))); } public void test_localeFromString() throws Exception { @@ -73,7 +75,7 @@ public class ICUTest extends junit.framework.TestCase { } private String best(Locale l, String skeleton) { - return ICU.getBestDateTimePattern(skeleton, l.toString()); + return ICU.getBestDateTimePattern(skeleton, l); } public void test_getDateFormatOrder() throws Exception { @@ -123,4 +125,95 @@ public class ICUTest extends junit.framework.TestCase { } catch (IllegalArgumentException expected) { } } + + public void testScriptsPassedToIcu() throws Exception { + Locale sr_Cyrl_BA = Locale.forLanguageTag("sr-Cyrl-BA"); + Locale sr_Cyrl_ME = Locale.forLanguageTag("sr-Cyrl-ME"); + Locale sr_Latn_BA = Locale.forLanguageTag("sr-Latn-BA"); + Locale sr_Latn_ME = Locale.forLanguageTag("sr-Latn-ME"); + + assertEquals("sr_BA_#Cyrl", sr_Cyrl_BA.toString()); + assertEquals("Cyrl", sr_Cyrl_BA.getScript()); + + assertEquals("sr_ME_#Cyrl", sr_Cyrl_ME.toString()); + assertEquals("Cyrl", sr_Cyrl_ME.getScript()); + + assertEquals("sr_BA_#Latn", sr_Latn_BA.toString()); + assertEquals("Latn", sr_Latn_BA.getScript()); + + assertEquals("sr_ME_#Latn", sr_Latn_ME.toString()); + assertEquals("Latn", sr_Latn_ME.getScript()); + + assertEquals("Српски", sr_Cyrl_BA.getDisplayLanguage(sr_Cyrl_BA)); + assertEquals("Босна и Херцеговина", sr_Cyrl_BA.getDisplayCountry(sr_Cyrl_BA)); + assertEquals("Ћирилица", sr_Cyrl_BA.getDisplayScript(sr_Cyrl_BA)); + assertEquals("", sr_Cyrl_BA.getDisplayVariant(sr_Cyrl_BA)); + + assertEquals("Српски", sr_Cyrl_ME.getDisplayLanguage(sr_Cyrl_ME)); + assertEquals("Црна Гора", sr_Cyrl_ME.getDisplayCountry(sr_Cyrl_ME)); + assertEquals("Ћирилица", sr_Cyrl_ME.getDisplayScript(sr_Cyrl_ME)); + assertEquals("", sr_Cyrl_ME.getDisplayVariant(sr_Cyrl_ME)); + + assertEquals("Srpski", sr_Latn_BA.getDisplayLanguage(sr_Latn_BA)); + assertEquals("Bosna i Hercegovina", sr_Latn_BA.getDisplayCountry(sr_Latn_BA)); + assertEquals("Latinica", sr_Latn_BA.getDisplayScript(sr_Latn_BA)); + assertEquals("", sr_Latn_BA.getDisplayVariant(sr_Latn_BA)); + + assertEquals("Srpski", sr_Latn_ME.getDisplayLanguage(sr_Latn_ME)); + assertEquals("Crna Gora", sr_Latn_ME.getDisplayCountry(sr_Latn_ME)); + assertEquals("Latinica", sr_Latn_ME.getDisplayScript(sr_Latn_ME)); + assertEquals("", sr_Latn_ME.getDisplayVariant(sr_Latn_ME)); + + assertEquals("BIH", sr_Cyrl_BA.getISO3Country()); + assertEquals("srp", sr_Cyrl_BA.getISO3Language()); + assertEquals("MNE", sr_Cyrl_ME.getISO3Country()); + assertEquals("srp", sr_Cyrl_ME.getISO3Language()); + assertEquals("BIH", sr_Latn_BA.getISO3Country()); + assertEquals("srp", sr_Latn_BA.getISO3Language()); + assertEquals("MNE", sr_Latn_ME.getISO3Country()); + assertEquals("srp", sr_Latn_ME.getISO3Language()); + + BreakIterator.getCharacterInstance(sr_Cyrl_BA); + BreakIterator.getCharacterInstance(sr_Cyrl_ME); + BreakIterator.getCharacterInstance(sr_Latn_BA); + BreakIterator.getCharacterInstance(sr_Latn_ME); + + BreakIterator.getLineInstance(sr_Cyrl_BA); + BreakIterator.getLineInstance(sr_Cyrl_ME); + BreakIterator.getLineInstance(sr_Latn_BA); + BreakIterator.getLineInstance(sr_Latn_ME); + + BreakIterator.getSentenceInstance(sr_Cyrl_BA); + BreakIterator.getSentenceInstance(sr_Cyrl_ME); + BreakIterator.getSentenceInstance(sr_Latn_BA); + BreakIterator.getSentenceInstance(sr_Latn_ME); + + BreakIterator.getWordInstance(sr_Cyrl_BA); + BreakIterator.getWordInstance(sr_Cyrl_ME); + BreakIterator.getWordInstance(sr_Latn_BA); + BreakIterator.getWordInstance(sr_Latn_ME); + + Collator.getInstance(sr_Cyrl_BA); + Collator.getInstance(sr_Cyrl_ME); + Collator.getInstance(sr_Latn_BA); + Collator.getInstance(sr_Latn_ME); + + // TODO: This needs to be fixed. We shouldn't output attribute key + // expansions in the language tag or the toString output. The tests + // will fail with something like: + // + // expected:<de-u-co[-phonebk-kf-upper-kn]> but was: + // <de-u-co[lcasefirst-upper-collation-phonebook-colnumeric-yes]> + Locale l = Locale.forLanguageTag("de-u-co-phonebk-kf-upper-kn"); + assertEquals("de__#u-co-phonebk-kf-upper-kn", l.toString()); + assertEquals("de-u-co-phonebk-kf-upper-kn", l.toLanguageTag()); + + Collator c = Collator.getInstance(l); + assertTrue(c.compare("2", "11") < 0); + assertTrue(c.compare("11", "ae") < 0); + assertTrue(c.compare("ae", "Ä") < 0); + assertTrue(c.compare("Ä", "ä") < 0); + assertTrue(c.compare("ä", "AF") < 0); + assertTrue(c.compare("AF", "af") < 0); + } } diff --git a/luni/src/test/java/libcore/java/util/LocaleTest.java b/luni/src/test/java/libcore/java/util/LocaleTest.java index b87dc97..0512b13 100644 --- a/luni/src/test/java/libcore/java/util/LocaleTest.java +++ b/luni/src/test/java/libcore/java/util/LocaleTest.java @@ -39,9 +39,22 @@ public class LocaleTest extends junit.framework.TestCase { // and variant, but a display name made up of the raw strings. // Newer releases return slightly different results, but no less unreasonable. assertEquals("aabbcc", invalid.getDisplayLanguage()); - assertEquals("", invalid.getDisplayCountry()); - assertEquals("DDEEFF_GGHHII", invalid.getDisplayVariant()); - assertEquals("aabbcc (DDEEFF,DDEEFF_GGHHII)", invalid.getDisplayName()); + assertEquals("DDEEFF", invalid.getDisplayCountry()); + assertEquals("GGHHII", invalid.getDisplayVariant()); + assertEquals("aabbcc (DDEEFF,GGHHII)", invalid.getDisplayName()); + } + + public void test_getDisplayName_emptyCodes() { + Locale emptyLanguage = new Locale("", "DdeEFf"); + assertEquals("", emptyLanguage.getDisplayLanguage()); + + Locale emptyCountry = new Locale("AaBbCc", ""); + assertEquals("", emptyCountry.getDisplayCountry()); + + Locale emptyCountryAndLanguage = new Locale("", "", "Farl"); + assertEquals("", emptyCountryAndLanguage.getDisplayLanguage()); + assertEquals("", emptyCountryAndLanguage.getDisplayCountry()); + assertEquals("Farl", emptyCountryAndLanguage.getDisplayVariant()); } // http://b/2611311; if there's no display language/country/variant, use the raw codes. @@ -53,8 +66,8 @@ public class LocaleTest extends junit.framework.TestCase { assertEquals("xx", unknown.getDisplayLanguage()); assertEquals("YY", unknown.getDisplayCountry()); - assertEquals("TRADITIONAL", unknown.getDisplayVariant()); - assertEquals("xx (YY,TRADITIONAL)", unknown.getDisplayName()); + assertEquals("Traditional", unknown.getDisplayVariant()); + assertEquals("xx (YY,Traditional)", unknown.getDisplayName()); } public void test_getDisplayName_easy() throws Exception { |