diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java b/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java index f6e8f0bfdeb..ad039a5ba85 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/util/ULocale.java @@ -1,6 +1,6 @@ /* ****************************************************************************** -* Copyright (C) 2003-2013, International Business Machines Corporation and +* Copyright (C) 2003-2014, International Business Machines Corporation and * others. All Rights Reserved. ****************************************************************************** */ @@ -107,7 +107,7 @@ import com.ibm.icu.text.LocaleDisplayNames.DialectHandling; * @author Ram Viswanadha * @stable ICU 2.8 */ -public final class ULocale implements Serializable { +public final class ULocale implements Serializable, Comparable { // using serialver from jdk1.4.2_05 private static final long serialVersionUID = 3715177670352309217L; @@ -775,6 +775,85 @@ public final class ULocale implements Serializable { return false; } + /** + * Compares two ULocale for ordering. + *

Note: The order might change in future.

+ * + * @param other the ULocale to be compared. + * @return a negative integer, zero, or a positive integer as this ULocale is less than, equal to, or greater + * than the specified ULocale. + * @throws NullPointerException if other is null. + * + * @draft ICU 53 + * @provisional This API might change or be removed in a future release. + */ + public int compareTo(ULocale other) { + if (this == other) { + return 0; + } + + int cmp = 0; + + // Language + cmp = getLanguage().compareTo(other.getLanguage()); + if (cmp == 0) { + // Script + cmp = getScript().compareTo(other.getScript()); + if (cmp == 0) { + // Region + cmp = getCountry().compareTo(other.getCountry()); + if (cmp == 0) { + // Variant + cmp = getVariant().compareTo(other.getVariant()); + if (cmp == 0) { + // Keywords + Iterator thisKwdItr = getKeywords(); + Iterator otherKwdItr = other.getKeywords(); + + if (thisKwdItr == null) { + cmp = otherKwdItr == null ? 0 : -1; + } else if (otherKwdItr == null) { + cmp = 1; + } else { + // Both have keywords + while (cmp == 0 && thisKwdItr.hasNext()) { + if (!otherKwdItr.hasNext()) { + cmp = 1; + break; + } + // Compare keyword keys + String thisKey = thisKwdItr.next(); + String otherKey = otherKwdItr.next(); + cmp = thisKey.compareTo(otherKey); + if (cmp == 0) { + // Compare keyword values + String thisVal = getKeywordValue(thisKey); + String otherVal = other.getKeywordValue(otherKey); + if (thisVal == null) { + cmp = otherVal == null ? 0 : -1; + } else if (otherVal == null) { + cmp = 1; + } else { + cmp = thisVal.compareTo(otherVal); + } + } + } + if (cmp == 0 && otherKwdItr.hasNext()) { + cmp = -1; + } + } + } + } + } + } + + // Normalize the result value: + // Note: String.compareTo() may return value other than -1, 0, 1. + // A value other than those are OK by the definition, but we don't want + // associate any semantics other than negative/zero/positive. + return (cmp < 0) ? -1 : ((cmp > 0) ? 1 : 0); + } + /** * {@icunote} Unlike the Locale API, this returns an array of ULocale, * not Locale. Returns a list of all installed locales. diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java index c6d933d7337..374e011bdd2 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/util/ULocaleTest.java @@ -1,6 +1,6 @@ /* ********************************************************************** - * Copyright (c) 2004-2013, International Business Machines + * Copyright (c) 2004-2014, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Alan Liu @@ -19,6 +19,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.lang.UCharacter; @@ -4328,4 +4329,118 @@ public class ULocaleTest extends TestFmwk { // Restore back up Locale.setDefault(backupDefault); } + + // + // Test case for the behavior of Comparable implementation. + // + public void TestComparable() { + // Test strings used for creating ULocale objects. + // This list contains multiple different strings creating + // multiple equivalent locales. + final String[] localeStrings = { + "en", + "EN", + "en_US", + "en_GB", + "en_US_POSIX", + "en_us_posix", + "ar_EG", + "zh_Hans_CN", + "zh_Hant_TW", + "zh_Hans", + "zh_CN", + "zh_TW", + "th_TH@calendar=buddhist;numbers=thai", + "TH_TH@NUMBERS=thai;CALENDAR=buddhist", + "th_TH@calendar=buddhist", + "th_TH@calendar=gergorian", + "th_TH@numbers=latn", + "abc_def_ghi_jkl_opq", + "abc_DEF_ghi_JKL_opq", + "", + "und", + "This is a bogus locale ID", + "This is a BOGUS locale ID", + "en_POSIX", + "en__POSIX", + }; + + ULocale[] locales = new ULocale[localeStrings.length]; + for (int i = 0; i < locales.length; i++) { + locales[i] = new ULocale(localeStrings[i]); + } + + // compares all permutations + for (int i = 0; i < locales.length; i++) { + for (int j = i /* including the locale itself */; j < locales.length; j++) { + boolean eqls1 = locales[i].equals(locales[j]); + boolean eqls2 = locales[i].equals(locales[j]); + + if (eqls1 != eqls2) { + errln("FAILED: loc1.equals(loc2) and loc2.equals(loc1) return different results: loc1=" + + locales[i] + ", loc2=" + locales[j]); + } + + int cmp1 = locales[i].compareTo(locales[j]); + int cmp2 = locales[j].compareTo(locales[i]); + + if ((cmp1 == 0) != eqls1) { + errln("FAILED: inconsistent equals and compareTo: loc1=" + + locales[i] + ", loc2=" + locales[j]); + } + if (cmp1 < 0 && cmp2 <= 0 || cmp1 > 0 && cmp2 >= 0 || cmp1 == 0 && cmp2 != 0) { + errln("FAILED: loc1.compareTo(loc2) is inconsistent with loc2.compareTo(loc1): loc1=" + + locales[i] + ", loc2=" + locales[j]); + } + } + } + + // Make sure ULocale objects can be sorted by the Java collection + // framework class without providing a Comparator, and equals/compareTo + // are consistent. + + // The sorted locale list created from localeStrings above. + // Duplicated locales are removed and locale string is normalized + // (by the ULocale constructor). + final String[] sortedLocaleStrings = { + "", + "abc_DEF_GHI_JKL_OPQ", + "ar_EG", + "en", + "en__POSIX", + "en_GB", + "en_US", + "en_US_POSIX", + "th_TH@calendar=buddhist", + "th_TH@calendar=buddhist;numbers=thai", + "th_TH@calendar=gergorian", + "th_TH@numbers=latn", + "this is a bogus locale id", + "und", + "zh_CN", + "zh_TW", + "zh_Hans", + "zh_Hans_CN", + "zh_Hant_TW", + }; + + TreeSet sortedLocales = new TreeSet(); + for (ULocale locale : locales) { + sortedLocales.add(locale); + } + + // Check the number of unique locales + if (sortedLocales.size() != sortedLocaleStrings.length) { + errln("FAILED: Number of unique locales: " + sortedLocales.size() + ", expected: " + sortedLocaleStrings.length); + } + + // Check the order + int i = 0; + for (ULocale loc : sortedLocales) { + if (!loc.toString().equals(sortedLocaleStrings[i++])) { + errln("FAILED: Sort order is incorrect for " + loc.toString()); + break; + } + } + } }