diff --git a/icu4c/source/common/localematcher.cpp b/icu4c/source/common/localematcher.cpp index 381df21265..f5f3a62708 100644 --- a/icu4c/source/common/localematcher.cpp +++ b/icu4c/source/common/localematcher.cpp @@ -12,6 +12,7 @@ #include "unicode/localematcher.h" #include "unicode/locid.h" #include "unicode/stringpiece.h" +#include "unicode/uloc.h" #include "unicode/uobject.h" #include "cstring.h" #include "localeprioritylist.h" @@ -20,6 +21,7 @@ #include "lsr.h" #include "uassert.h" #include "uhash.h" +#include "ustr_imp.h" #include "uvector.h" #define UND_LSR LSR("und", "", "", LSR::EXPLICIT_LSR) @@ -688,4 +690,97 @@ double LocaleMatcher::internalMatch(const Locale &desired, const Locale &support U_NAMESPACE_END +// uloc_acceptLanguage() --------------------------------------------------- *** + +U_NAMESPACE_USE + +namespace { + +class LocaleFromTag { +public: + LocaleFromTag() : locale(Locale::getRoot()) {} + const Locale &operator()(const char *tag) { return locale = Locale(tag); } + +private: + // Store the locale in the converter, rather than return a reference to a temporary, + // or a value which could go out of scope with the caller's reference to it. + Locale locale; +}; + +int32_t acceptLanguage(UEnumeration &supportedLocales, Locale::Iterator &desiredLocales, + char *dest, int32_t capacity, UAcceptResult *acceptResult, + UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return 0; } + LocaleMatcher::Builder builder; + const char *locString; + while ((locString = uenum_next(&supportedLocales, nullptr, &errorCode)) != nullptr) { + Locale loc(locString); + if (loc.isBogus()) { + errorCode = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + builder.addSupportedLocale(loc); + } + LocaleMatcher matcher = builder.build(errorCode); + LocaleMatcher::Result result = matcher.getBestMatchResult(desiredLocales, errorCode); + if (U_FAILURE(errorCode)) { return 0; } + if (result.getDesiredIndex() >= 0) { + if (acceptResult != nullptr) { + *acceptResult = *result.getDesiredLocale() == *result.getSupportedLocale() ? + ULOC_ACCEPT_VALID : ULOC_ACCEPT_FALLBACK; + } + const char *bestStr = result.getSupportedLocale()->getName(); + int32_t bestLength = (int32_t)uprv_strlen(bestStr); + if (bestLength <= capacity) { + uprv_memcpy(dest, bestStr, bestLength); + } + return u_terminateChars(dest, capacity, bestLength, &errorCode); + } else { + if (acceptResult != nullptr) { + *acceptResult = ULOC_ACCEPT_FAILED; + } + return u_terminateChars(dest, capacity, 0, &errorCode); + } +} + +} // namespace + +U_CAPI int32_t U_EXPORT2 +uloc_acceptLanguage(char *result, int32_t resultAvailable, + UAcceptResult *outResult, + const char **acceptList, int32_t acceptListCount, + UEnumeration *availableLocales, + UErrorCode *status) { + if (U_FAILURE(*status)) { return 0; } + if ((result == nullptr ? resultAvailable != 0 : resultAvailable < 0) || + (acceptList == nullptr ? acceptListCount != 0 : acceptListCount < 0) || + availableLocales == nullptr) { + *status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + LocaleFromTag converter; + Locale::ConvertingIterator desiredLocales( + acceptList, acceptList + acceptListCount, converter); + return acceptLanguage(*availableLocales, desiredLocales, + result, resultAvailable, outResult, *status); +} + +U_CAPI int32_t U_EXPORT2 +uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, + UAcceptResult *outResult, + const char *httpAcceptLanguage, + UEnumeration *availableLocales, + UErrorCode *status) { + if (U_FAILURE(*status)) { return 0; } + if ((result == nullptr ? resultAvailable != 0 : resultAvailable < 0) || + httpAcceptLanguage == nullptr || availableLocales == nullptr) { + *status = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + LocalePriorityList list(httpAcceptLanguage, *status); + LocalePriorityList::Iterator desiredLocales = list.iterator(); + return acceptLanguage(*availableLocales, desiredLocales, + result, resultAvailable, outResult, *status); +} + #endif // __LOCMATCHER_H__ diff --git a/icu4c/source/common/localeprioritylist.cpp b/icu4c/source/common/localeprioritylist.cpp index 06442fb46a..cee408269c 100644 --- a/icu4c/source/common/localeprioritylist.cpp +++ b/icu4c/source/common/localeprioritylist.cpp @@ -133,7 +133,7 @@ LocalePriorityList::LocalePriorityList(StringPiece s, UErrorCode &errorCode) { if (U_FAILURE(errorCode)) { return; } Locale locale = Locale(tag.data()); if (locale.isBogus()) { - errorCode = U_MEMORY_ALLOCATION_ERROR; + errorCode = U_ILLEGAL_ARGUMENT_ERROR; return; } int32_t weight = WEIGHT_ONE; diff --git a/icu4c/source/common/uloc.cpp b/icu4c/source/common/uloc.cpp index 687390c5b6..28cee286e2 100644 --- a/icu4c/source/common/uloc.cpp +++ b/icu4c/source/common/uloc.cpp @@ -2127,301 +2127,6 @@ uloc_getISOCountries() return COUNTRIES; } - -/* this function to be moved into cstring.c later */ -static char gDecimal = 0; - -static /* U_CAPI */ -double -/* U_EXPORT2 */ -_uloc_strtod(const char *start, char **end) { - char *decimal; - char *myEnd; - char buf[30]; - double rv; - if (!gDecimal) { - char rep[5]; - /* For machines that decide to change the decimal on you, - and try to be too smart with localization. - This normally should be just a '.'. */ - sprintf(rep, "%+1.1f", 1.0); - gDecimal = rep[2]; - } - - if(gDecimal == '.') { - return uprv_strtod(start, end); /* fall through to OS */ - } else { - uprv_strncpy(buf, start, 29); - buf[29]=0; - decimal = uprv_strchr(buf, '.'); - if(decimal) { - *decimal = gDecimal; - } else { - return uprv_strtod(start, end); /* no decimal point */ - } - rv = uprv_strtod(buf, &myEnd); - if(end) { - *end = (char*)(start+(myEnd-buf)); /* cast away const (to follow uprv_strtod API.) */ - } - return rv; - } -} - -typedef struct { - float q; - int32_t dummy; /* to avoid uninitialized memory copy from qsort */ - char locale[ULOC_FULLNAME_CAPACITY+1]; -} _acceptLangItem; - -static int32_t U_CALLCONV -uloc_acceptLanguageCompare(const void * /*context*/, const void *a, const void *b) -{ - const _acceptLangItem *aa = (const _acceptLangItem*)a; - const _acceptLangItem *bb = (const _acceptLangItem*)b; - - int32_t rc = 0; - if(bb->q < aa->q) { - rc = -1; /* A > B */ - } else if(bb->q > aa->q) { - rc = 1; /* A < B */ - } else { - rc = 0; /* A = B */ - } - - if(rc==0) { - rc = uprv_stricmp(aa->locale, bb->locale); - } - -#if defined(ULOC_DEBUG) - /* fprintf(stderr, "a:[%s:%g], b:[%s:%g] -> %d\n", - aa->locale, aa->q, - bb->locale, bb->q, - rc);*/ -#endif - - return rc; -} - -/* -mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53 -*/ - -U_CAPI int32_t U_EXPORT2 -uloc_acceptLanguageFromHTTP(char *result, int32_t resultAvailable, UAcceptResult *outResult, - const char *httpAcceptLanguage, - UEnumeration* availableLocales, - UErrorCode *status) -{ - MaybeStackArray<_acceptLangItem, 4> items; // Struct for collecting items. - char tmp[ULOC_FULLNAME_CAPACITY +1]; - int32_t n = 0; - const char *itemEnd; - const char *paramEnd; - const char *s; - const char *t; - int32_t res; - int32_t i; - int32_t l = (int32_t)uprv_strlen(httpAcceptLanguage); - - if(U_FAILURE(*status)) { - return -1; - } - - for(s=httpAcceptLanguage;s&&*s;) { - while(isspace(*s)) /* eat space at the beginning */ - s++; - itemEnd=uprv_strchr(s,','); - paramEnd=uprv_strchr(s,';'); - if(!itemEnd) { - itemEnd = httpAcceptLanguage+l; /* end of string */ - } - if(paramEnd && paramEnds)&&isspace(*t);t--) - ; - int32_t slen = static_cast(((t+1)-s)); - if(slen > ULOC_FULLNAME_CAPACITY) { - *status = U_BUFFER_OVERFLOW_ERROR; - return -1; // too big - } - uprv_strncpy(items[n].locale, s, slen); - items[n].locale[slen]=0; // terminate - int32_t clen = uloc_canonicalize(items[n].locale, tmp, UPRV_LENGTHOF(tmp)-1, status); - if(U_FAILURE(*status)) return -1; - if((clen!=slen) || (uprv_strncmp(items[n].locale, tmp, slen))) { - // canonicalization had an effect- copy back - uprv_strncpy(items[n].locale, tmp, clen); - items[n].locale[clen] = 0; // terminate - } -#if defined(ULOC_DEBUG) - /*fprintf(stderr,"%d: s <%s> q <%g>\n", n, j[n].locale, j[n].q);*/ -#endif - n++; - s = itemEnd; - while(*s==',') { /* eat duplicate commas */ - s++; - } - if(n>=items.getCapacity()) { // If we need more items - if(NULL == items.resize(items.getCapacity()*2, items.getCapacity())) { - *status = U_MEMORY_ALLOCATION_ERROR; - return -1; - } -#if defined(ULOC_DEBUG) - fprintf(stderr,"malloced at size %d\n", items.getCapacity()); -#endif - } - } - uprv_sortArray(items.getAlias(), n, sizeof(items[0]), uloc_acceptLanguageCompare, NULL, TRUE, status); - if (U_FAILURE(*status)) { - return -1; - } - LocalMemory strs(NULL); - if (strs.allocateInsteadAndReset(n) == NULL) { - *status = U_MEMORY_ALLOCATION_ERROR; - return -1; - } - for(i=0;i q <%g>\n", i, j[i].locale, j[i].q);*/ -#endif - strs[i]=items[i].locale; - } - res = uloc_acceptLanguage(result, resultAvailable, outResult, - strs.getAlias(), n, availableLocales, status); - return res; -} - - -U_CAPI int32_t U_EXPORT2 -uloc_acceptLanguage(char *result, int32_t resultAvailable, - UAcceptResult *outResult, const char **acceptList, - int32_t acceptListCount, - UEnumeration* availableLocales, - UErrorCode *status) -{ - int32_t i,j; - int32_t len; - int32_t maxLen=0; - char tmp[ULOC_FULLNAME_CAPACITY+1]; - const char *l; - char **fallbackList; - if(U_FAILURE(*status)) { - return -1; - } - fallbackList = static_cast(uprv_malloc((size_t)(sizeof(fallbackList[0])*acceptListCount))); - if(fallbackList==NULL) { - *status = U_MEMORY_ALLOCATION_ERROR; - return -1; - } - for(i=0;i0) { - uprv_strncpy(result, l, uprv_min(len, resultAvailable)); - } - for(j=0;jmaxLen) { - maxLen = len; - } - } - uenum_reset(availableLocales, status); - /* save off parent info */ - if(uloc_getParent(acceptList[i], tmp, UPRV_LENGTHOF(tmp), status)!=0) { - fallbackList[i] = uprv_strdup(tmp); - } else { - fallbackList[i]=0; - } - } - - for(maxLen--;maxLen>0;maxLen--) { - for(i=0;i0) { - uprv_strncpy(result, l, uprv_min(len, resultAvailable)); - } - for(j=0;j0)&&uprv_strcmp(tmp, tests[i].expect)) { - log_err_status(status, "FAIL: #%d: expected %s but got %s\n", i, tests[i].expect, tmp); + log_err_status(status, + "FAIL: #%d: expected %s but got %s\n", + i, tests[i].expect, tmp); log_info("test #%d: http[%s], ICU[%s], expect %s, %s\n", i, http[tests[i].httpSet], tests[i].icuSet, tests[i].expect, acceptResult(tests[i].res)); } diff --git a/icu4j/main/classes/collate/src/com/ibm/icu/util/GlobalizationPreferences.java b/icu4j/main/classes/collate/src/com/ibm/icu/util/GlobalizationPreferences.java index 13c9e081bd..5c3699c0e4 100644 --- a/icu4j/main/classes/collate/src/com/ibm/icu/util/GlobalizationPreferences.java +++ b/icu4j/main/classes/collate/src/com/ibm/icu/util/GlobalizationPreferences.java @@ -8,7 +8,6 @@ */ package com.ibm.icu.util; -import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -18,6 +17,7 @@ import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.Set; import com.ibm.icu.impl.Utility; import com.ibm.icu.text.BreakIterator; @@ -259,14 +259,10 @@ public class GlobalizationPreferences implements Freezable acceptSet = LocalePriorityList.add(acceptLanguageString).build().getULocales(); + // processLocales() wants a List even though it only iterates front-to-back. + locales = processLocales(new ArrayList<>(acceptSet)); + return this; } /** @@ -815,6 +811,9 @@ public class GlobalizationPreferences implements Freezable processLocales(List inputLocales) { + // Note: Some of the callers, and non-ICU call sites, could be simpler/more efficient + // if this method took a Collection or even an Iterable. + // Maybe we can change it since this is still @draft and probably not widely overridden. List result = new ArrayList<>(); /* * Step 1: Relocate later occurrence of more specific locale @@ -824,9 +823,7 @@ public class GlobalizationPreferences implements Freezable { this.locale = locale; } - /** - * Construct a ULocale object from a {@link java.util.Locale}. - * @param loc a {@link java.util.Locale} - */ - private ULocale(Locale loc) { - this.localeID = getName(forLocale(loc).toString()); - this.locale = loc; - } - /** * {@icu} Returns a ULocale object for a {@link java.util.Locale}. * The ULocale is canonicalized. @@ -2130,28 +2119,42 @@ public final class ULocale implements Serializable, Comparable { * ROOT ULocale if if a ROOT locale was used as a fallback (because nothing else in * availableLocales matched). No ULocale array element should be null; behavior is * undefined if this is the case. + * + *

This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}. + * * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales * @param availableLocales list of available locales. One of these will be returned. * @param fallback if non-null, a 1-element array containing a boolean to be set with * the fallback status * @return one of the locales from the availableLocales list, or null if none match * @stable ICU 3.4 + * @see LocaleMatcher + * @see LocalePriorityList */ public static ULocale acceptLanguage(String acceptLanguageList, ULocale[] availableLocales, boolean[] fallback) { - if (acceptLanguageList == null) { - throw new NullPointerException(); + if (fallback != null) { + fallback[0] = true; } - ULocale acceptList[] = null; + LocalePriorityList desired; try { - acceptList = parseAcceptLanguage(acceptLanguageList, true); - } catch (ParseException pe) { - acceptList = null; - } - if (acceptList == null) { + desired = LocalePriorityList.add(acceptLanguageList).build(); + } catch (IllegalArgumentException e) { return null; } - return acceptLanguage(acceptList, availableLocales, fallback); + LocaleMatcher.Builder builder = LocaleMatcher.builder(); + for (ULocale locale : availableLocales) { + builder.addSupportedULocale(locale); + } + LocaleMatcher matcher = builder.build(); + LocaleMatcher.Result result = matcher.getBestMatchResult(desired); + if (result.getDesiredIndex() >= 0) { + if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) { + fallback[0] = false; + } + return result.getSupportedULocale(); + } + return null; } /** @@ -2162,57 +2165,39 @@ public final class ULocale implements Serializable, Comparable { * will be one of the locales in availableLocales, or the ROOT ULocale if if a ROOT * locale was used as a fallback (because nothing else in availableLocales matched). * No ULocale array element should be null; behavior is undefined if this is the case. + * + *

This is a thin wrapper over {@link LocaleMatcher}. + * * @param acceptLanguageList list of acceptable locales * @param availableLocales list of available locales. One of these will be returned. * @param fallback if non-null, a 1-element array containing a boolean to be set with * the fallback status * @return one of the locales from the availableLocales list, or null if none match * @stable ICU 3.4 + * @see LocaleMatcher */ - public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[] - availableLocales, boolean[] fallback) { - // fallbacklist - int i,j; - if(fallback != null) { - fallback[0]=true; + public static ULocale acceptLanguage(ULocale[] acceptLanguageList, ULocale[] availableLocales, + boolean[] fallback) { + if (fallback != null) { + fallback[0] = true; } - for(i=0;i 0 - && availableLocales[j].getLanguage().equals(aLocale.getLanguage()) - && availableLocales[j].getCountry().equals(aLocale.getCountry()) - && availableLocales[j].getVariant().equals(aLocale.getVariant())) { - ULocale minAvail = ULocale.minimizeSubtags(availableLocales[j]); - if (minAvail.getScript().length() == 0) { - if(setFallback != null) { - setFallback[0] = false; // not a fallback. - } - return aLocale; - } - } - } - Locale loc = aLocale.toLocale(); - Locale parent = LocaleUtility.fallback(loc); - if(parent != null) { - aLocale = new ULocale(parent); - } else { - aLocale = null; - } - setFallback = null; // Do not set fallback in later iterations - } while (aLocale != null); + LocaleMatcher.Builder builder = LocaleMatcher.builder(); + for (ULocale locale : availableLocales) { + builder.addSupportedULocale(locale); + } + LocaleMatcher matcher = builder.build(); + LocaleMatcher.Result result; + if (acceptLanguageList.length == 1) { + result = matcher.getBestMatchResult(acceptLanguageList[0]); + } else { + result = matcher.getBestMatchResult(Arrays.asList(acceptLanguageList)); + } + if (result.getDesiredIndex() >= 0) { + if (fallback != null && result.getDesiredULocale().equals(result.getSupportedULocale())) { + fallback[0] = false; + } + return result.getSupportedULocale(); } return null; } @@ -2227,12 +2212,17 @@ public final class ULocale implements Serializable, Comparable { * availableLocales matched). No ULocale array element should be null; behavior is * undefined if this is the case. This function will choose a locale from the * ULocale.getAvailableLocales() list as available. + * + *

This is a thin wrapper over {@link LocalePriorityList} + {@link LocaleMatcher}. + * * @param acceptLanguageList list in HTTP "Accept-Language:" format of acceptable locales * @param fallback if non-null, a 1-element array containing a boolean to be set with * the fallback status * @return one of the locales from the ULocale.getAvailableLocales() list, or null if * none match * @stable ICU 3.4 + * @see LocaleMatcher + * @see LocalePriorityList */ public static ULocale acceptLanguage(String acceptLanguageList, boolean[] fallback) { return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(), @@ -2249,275 +2239,21 @@ public final class ULocale implements Serializable, Comparable { * availableLocales matched). No ULocale array element should be null; behavior is * undefined if this is the case. This function will choose a locale from the * ULocale.getAvailableLocales() list as available. + * + *

This is a thin wrapper over {@link LocaleMatcher}. + * * @param acceptLanguageList ordered array of acceptable locales (preferred are listed first) * @param fallback if non-null, a 1-element array containing a boolean to be set with * the fallback status * @return one of the locales from the ULocale.getAvailableLocales() list, or null if none match * @stable ICU 3.4 + * @see LocaleMatcher */ public static ULocale acceptLanguage(ULocale[] acceptLanguageList, boolean[] fallback) { return acceptLanguage(acceptLanguageList, ULocale.getAvailableLocales(), fallback); } - /** - * Package local method used for parsing Accept-Language string - */ - static ULocale[] parseAcceptLanguage(String acceptLanguage, boolean isLenient) - throws ParseException { - class ULocaleAcceptLanguageQ implements Comparable { - private double q; - private double serial; - public ULocaleAcceptLanguageQ(double theq, int theserial) { - q = theq; - serial = theserial; - } - @Override - public int compareTo(ULocaleAcceptLanguageQ other) { - if (q > other.q) { // reverse - to sort in descending order - return -1; - } else if (q < other.q) { - return 1; - } - if (serial < other.serial) { - return -1; - } else if (serial > other.serial) { - return 1; - } else { - return 0; // same object - } - } - } - - // parse out the acceptLanguage into an array - TreeMap map = - new TreeMap<>(); - StringBuilder languageRangeBuf = new StringBuilder(); - StringBuilder qvalBuf = new StringBuilder(); - int state = 0; - acceptLanguage += ","; // append comma to simplify the parsing code - int n; - boolean subTag = false; - boolean q1 = false; - for (n = 0; n < acceptLanguage.length(); n++) { - boolean gotLanguageQ = false; - char c = acceptLanguage.charAt(n); - switch (state) { - case 0: // before language-range start - if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) { - // in language-range - languageRangeBuf.append(c); - state = 1; - subTag = false; - } else if (c == '*') { - languageRangeBuf.append(c); - state = 2; - } else if (c != ' ' && c != '\t') { - // invalid character - state = -1; - } - break; - case 1: // in language-range - if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) { - languageRangeBuf.append(c); - } else if (c == '-') { - subTag = true; - languageRangeBuf.append(c); - } else if (c == '_') { - if (isLenient) { - subTag = true; - languageRangeBuf.append(c); - } else { - state = -1; - } - } else if ('0' <= c && c <= '9') { - if (subTag) { - languageRangeBuf.append(c); - } else { - // DIGIT is allowed only in language sub tag - state = -1; - } - } else if (c == ',') { - // language-q end - gotLanguageQ = true; - } else if (c == ' ' || c == '\t') { - // language-range end - state = 3; - } else if (c == ';') { - // before q - state = 4; - } else { - // invalid character for language-range - state = -1; - } - break; - case 2: // saw wild card range - if (c == ',') { - // language-q end - gotLanguageQ = true; - } else if (c == ' ' || c == '\t') { - // language-range end - state = 3; - } else if (c == ';') { - // before q - state = 4; - } else { - // invalid - state = -1; - } - break; - case 3: // language-range end - if (c == ',') { - // language-q end - gotLanguageQ = true; - } else if (c == ';') { - // before q - state =4; - } else if (c != ' ' && c != '\t') { - // invalid - state = -1; - } - break; - case 4: // before q - if (c == 'q') { - // before equal - state = 5; - } else if (c != ' ' && c != '\t') { - // invalid - state = -1; - } - break; - case 5: // before equal - if (c == '=') { - // before q value - state = 6; - } else if (c != ' ' && c != '\t') { - // invalid - state = -1; - } - break; - case 6: // before q value - if (c == '0') { - // q value start with 0 - q1 = false; - qvalBuf.append(c); - state = 7; - } else if (c == '1') { - // q value start with 1 - qvalBuf.append(c); - state = 7; - } else if (c == '.') { - if (isLenient) { - qvalBuf.append(c); - state = 8; - } else { - state = -1; - } - } else if (c != ' ' && c != '\t') { - // invalid - state = -1; - } - break; - case 7: // q value start - if (c == '.') { - // before q value fraction part - qvalBuf.append(c); - state = 8; - } else if (c == ',') { - // language-q end - gotLanguageQ = true; - } else if (c == ' ' || c == '\t') { - // after q value - state = 10; - } else { - // invalid - state = -1; - } - break; - case 8: // before q value fraction part - if ('0' <= c && c <= '9') { - if (q1 && c != '0' && !isLenient) { - // if q value starts with 1, the fraction part must be 0 - state = -1; - } else { - // in q value fraction part - qvalBuf.append(c); - state = 9; - } - } else { - // invalid - state = -1; - } - break; - case 9: // in q value fraction part - if ('0' <= c && c <= '9') { - if (q1 && c != '0') { - // if q value starts with 1, the fraction part must be 0 - state = -1; - } else { - qvalBuf.append(c); - } - } else if (c == ',') { - // language-q end - gotLanguageQ = true; - } else if (c == ' ' || c == '\t') { - // after q value - state = 10; - } else { - // invalid - state = -1; - } - break; - case 10: // after q value - if (c == ',') { - // language-q end - gotLanguageQ = true; - } else if (c != ' ' && c != '\t') { - // invalid - state = -1; - } - break; - } - if (state == -1) { - // error state - throw new ParseException("Invalid Accept-Language", n); - } - if (gotLanguageQ) { - double q = 1.0; - if (qvalBuf.length() != 0) { - try { - q = Double.parseDouble(qvalBuf.toString()); - } catch (NumberFormatException nfe) { - // Already validated, so it should never happen - q = 1.0; - } - if (q > 1.0) { - q = 1.0; - } - } - if (languageRangeBuf.charAt(0) != '*') { - int serial = map.size(); - ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q, serial); - // sort in reverse order.. 1.0, 0.9, 0.8 .. etc - map.put(entry, new ULocale(canonicalize(languageRangeBuf.toString()))); - } - - // reset buffer and parse state - languageRangeBuf.setLength(0); - qvalBuf.setLength(0); - state = 0; - } - } - if (state != 0) { - // Well, the parser should handle all cases. So just in case. - throw new ParseException("Invalid AcceptlLanguage", n); - } - - // pull out the map - ULocale acceptList[] = map.values().toArray(new ULocale[map.size()]); - return acceptList; - } - private static final String UNDEFINED_LANGUAGE = "und"; private static final String UNDEFINED_SCRIPT = "Zzzz"; private static final String UNDEFINED_REGION = "ZZ"; @@ -3396,7 +3132,7 @@ public final class ULocale implements Serializable, Comparable { Listsubtags = tag.getVariants(); // ICU-20478: Sort variants per UTS35. - ArrayList variants = new ArrayList(subtags); + ArrayList variants = new ArrayList<>(subtags); Collections.sort(variants); for (String s : variants) { buf.append(LanguageTag.SEP); diff --git a/icu4j/main/tests/collate/src/com/ibm/icu/dev/test/format/GlobalizationPreferencesTest.java b/icu4j/main/tests/collate/src/com/ibm/icu/dev/test/format/GlobalizationPreferencesTest.java index 627d286f7e..cd5c746440 100644 --- a/icu4j/main/tests/collate/src/com/ibm/icu/dev/test/format/GlobalizationPreferencesTest.java +++ b/icu4j/main/tests/collate/src/com/ibm/icu/dev/test/format/GlobalizationPreferencesTest.java @@ -239,7 +239,7 @@ public class GlobalizationPreferencesTest extends TestFmwk { {"fr_CA", "fr"}, {"fr", "fr_CA"}, {"es", "fr", "en_US"}, - {"zh_Hans", "zh_Hans_CN"}, + {"zh_CN", "zh_Hans", "zh_Hans_CN"}, {"en_US_123"}, {"es_US", "es"}, {"de_DE", "es", "fr_FR"}, @@ -261,7 +261,7 @@ public class GlobalizationPreferencesTest extends TestFmwk { {"fr_CA", "fr"}, {"fr_CA", "fr"}, {"es", "fr", "en_US", "en"}, - {"zh_Hans_CN", "zh_Hans", "zh"}, + {"zh_Hans_CN", "zh_CN", "zh_Hans", "zh"}, {"en_US_123", "en_US", "en"}, {"es_US", "es"}, {"de_DE", "de", "es", "fr_FR", "fr"}, @@ -348,6 +348,11 @@ public class GlobalizationPreferencesTest extends TestFmwk { gp.setLocales(acceptLanguage); List resultLocales = gp.getLocales(); + List expectedLocales = new ArrayList<>(RESULTS_LOCALEIDS[i].length); + for (String exp : RESULTS_LOCALEIDS[i]) { + expectedLocales.add(new ULocale(exp)); + } + assertEquals("#" + i, expectedLocales.toString(), resultLocales.toString()); if (resultLocales.size() != RESULTS_LOCALEIDS[i].length) { StringBuilder res = new StringBuilder(); for (ULocale l : resultLocales) { @@ -377,22 +382,23 @@ public class GlobalizationPreferencesTest extends TestFmwk { } // Invalid accept-language - logln("Set locale - ko_KR"); - gp.setLocale(new ULocale("ko_KR")); - boolean bException = false; - try { - logln("Set invlaid accept-language - ko=100"); - gp.setLocales("ko=100"); - } catch (IllegalArgumentException iae) { - logln("IllegalArgumentException was thrown"); - bException = true; - } - if (!bException) { - errln("FAIL: IllegalArgumentException was not thrown for illegal accept-language - ko=100"); - } - if (!gp.getLocale(0).toString().equals("ko_KR")) { - errln("FAIL: Previous valid locale list had gone"); - } + // ICU-20700 changed the parser to using LocalePriorityList which is more lenient. +// logln("Set locale - ko_KR"); +// gp.setLocale(new ULocale("ko_KR")); +// boolean bException = false; +// try { +// logln("Set invlaid accept-language - ko=100"); +// gp.setLocales("ko=100"); +// } catch (IllegalArgumentException iae) { +// logln("IllegalArgumentException was thrown"); +// bException = true; +// } +// if (!bException) { +// errln("FAIL: IllegalArgumentException was not thrown for illegal accept-language - ko=100"); +// } +// if (!gp.getLocale(0).toString().equals("ko_KR")) { +// errln("FAIL: Previous valid locale list had gone"); +// } } @Test 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 0d00729578..4b7cc162cb 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 @@ -22,7 +22,6 @@ import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Pattern; @@ -34,7 +33,6 @@ import org.junit.runners.JUnit4; import com.ibm.icu.dev.test.TestFmwk; import com.ibm.icu.dev.test.TestUtil; import com.ibm.icu.dev.test.TestUtil.JavaVendor; -import com.ibm.icu.lang.UCharacter; import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.DecimalFormat; import com.ibm.icu.text.DisplayContext; @@ -46,6 +44,7 @@ import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.IllformedLocaleException; import com.ibm.icu.util.LocaleData; +import com.ibm.icu.util.LocalePriorityList; import com.ibm.icu.util.ULocale; import com.ibm.icu.util.ULocale.Builder; import com.ibm.icu.util.ULocale.Category; @@ -1590,15 +1589,15 @@ public class ULocaleTest extends TestFmwk { /*3*/ { null, "true" }, /*4*/ { "es", "false" }, /*5*/ { "de", "false" }, - /*6*/ { "zh_Hant_TW", "false" }, - /*7*/ { "zh", "true" }, + /*6*/ { "zh_Hant_TW", "true" }, + /*7*/ { "zh_Hant", "true" }, }; private static final String ACCEPT_LANGUAGE_HTTP[] = { /*0*/ "mt-mt, ja;q=0.76, en-us;q=0.95, en;q=0.92, en-gb;q=0.89, fr;q=0.87, iu-ca;q=0.84, iu;q=0.82, ja-jp;q=0.79, mt;q=0.97, de-de;q=0.74, de;q=0.71, es;q=0.68, it-it;q=0.66, it;q=0.63, vi-vn;q=0.61, vi;q=0.58, nl-nl;q=0.55, nl;q=0.53, th-th-traditional;q=.01", /*1*/ "ja;q=0.5, en;q=0.8, tlh", /*2*/ "en-zzz, de-lx;q=0.8", - /*3*/ "mga-ie;q=0.9, tlh", + /*3*/ "mga-ie;q=0.9, sux", /*4*/ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+ @@ -1610,16 +1609,16 @@ public class ULocaleTest extends TestFmwk { "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+ "xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, xxx-yyy;q=.01, "+ "es", - /*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8", - /*6*/ "zh-tw", - /*7*/ "zh-hant-cn", + /*5*/ "de;q=.9, fr;q=.9, xxx-yyy, sr;q=.8", + /*6*/ "zh-tw", + /*7*/ "zh-hant-cn", }; @Test public void TestAcceptLanguage() { for(int i = 0 ; i < (ACCEPT_LANGUAGE_HTTP.length); i++) { - Boolean expectBoolean = new Boolean(ACCEPT_LANGUAGE_TESTS[i][1]); + Boolean expectBoolean = Boolean.valueOf(ACCEPT_LANGUAGE_TESTS[i][1]); String expectLocale=ACCEPT_LANGUAGE_TESTS[i][0]; logln("#" + i + ": expecting: " + expectLocale + " (" + expectBoolean + ")"); @@ -1627,128 +1626,50 @@ public class ULocaleTest extends TestFmwk { boolean r[] = { false }; ULocale n = ULocale.acceptLanguage(ACCEPT_LANGUAGE_HTTP[i], r); if((n==null)&&(expectLocale!=null)) { - errln("result was null! line #" + i); + errln("#" + i + ": result was null!"); continue; } if(((n==null)&&(expectLocale==null)) || (n.toString().equals(expectLocale))) { - logln(" locale: OK." ); + logln("#" + i + ": locale: OK." ); } else { - errln("expected " + expectLocale + " but got " + n.toString()); + errln("#" + i + ": locale: expected " + expectLocale + " but got " + n); } - if(expectBoolean.equals(new Boolean(r[0]))) { - logln(" bool: OK."); + Boolean actualBoolean = Boolean.valueOf(r[0]); + if(expectBoolean.equals(actualBoolean)) { + logln("#" + i + ": fallback: OK."); } else { - errln("bool: not OK, was " + new Boolean(r[0]).toString() + " expected " + expectBoolean.toString()); + errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean); } } } - private ULocale[] StringToULocaleArray(String acceptLanguageList){ - //following code is copied from - //ULocale.acceptLanguage(String acceptLanguageList, ULocale[] availableLocales, boolean[] fallback) - class ULocaleAcceptLanguageQ implements Comparable { - private double q; - private double serial; - public ULocaleAcceptLanguageQ(double theq, int theserial) { - q = theq; - serial = theserial; - } - @Override - public int compareTo(Object o) { - ULocaleAcceptLanguageQ other = (ULocaleAcceptLanguageQ) o; - if(q > other.q) { // reverse - to sort in descending order - return -1; - } else if(q < other.q) { - return 1; - } - if(serial < other.serial) { - return -1; - } else if(serial > other.serial) { - return 1; - } else { - return 0; // same object - } - } - } - - // 1st: parse out the acceptLanguageList into an array - - TreeMap map = new TreeMap(); - - final int l = acceptLanguageList.length(); - int n; - for(n=0;n desiredSet = + LocalePriorityList.add(ACCEPT_LANGUAGE_HTTP[i]).build().getULocales(); + ULocale[] desiredArray = desiredSet.toArray(new ULocale[desiredSet.size()]); + ULocale n = ULocale.acceptLanguage(desiredArray, r); if((n==null)&&(expectLocale!=null)) { - errln("result was null! line #" + i); + errln("#" + i + ": result was null!"); continue; } if(((n==null)&&(expectLocale==null)) || (n.toString().equals(expectLocale))) { - logln(" locale: OK." ); + logln("#" + i + ": locale: OK."); } else { - errln("expected " + expectLocale + " but got " + n.toString()); + errln("#" + i + ": expected " + expectLocale + " but got " + n.toString()); } - if(expectBoolean.equals(new Boolean(r[0]))) { - logln(" bool: OK."); + Boolean actualBoolean = Boolean.valueOf(r[0]); + if(expectBoolean.equals(actualBoolean)) { + logln("#" + i + ": fallback: OK."); } else { - errln("bool: not OK, was " + new Boolean(r[0]).toString() + " expected " + expectBoolean.toString()); + errln("#" + i + ": fallback: was " + actualBoolean + " expected " + expectBoolean); } } }