ICU-20700 reimplement acceptLanguage() using the LocaleMatcher; replace older accept-language-string parsing by LocalePriorityList

This commit is contained in:
Markus Scherer 2020-03-06 17:23:17 -08:00
parent 3edff03393
commit d2ea4513dc
8 changed files with 244 additions and 773 deletions

View File

@ -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<const char **, LocaleFromTag> 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__

View File

@ -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;

View File

@ -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 && paramEnd<itemEnd) {
/* semicolon (;) is closer than end (,) */
t = paramEnd+1;
if(*t=='q') {
t++;
}
while(isspace(*t)) {
t++;
}
if(*t=='=') {
t++;
}
while(isspace(*t)) {
t++;
}
items[n].q = (float)_uloc_strtod(t,NULL);
} else {
/* no semicolon - it's 1.0 */
items[n].q = 1.0f;
paramEnd = itemEnd;
}
items[n].dummy=0;
/* eat spaces prior to semi */
for(t=(paramEnd-1);(paramEnd>s)&&isspace(*t);t--)
;
int32_t slen = static_cast<int32_t>(((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<const char*> strs(NULL);
if (strs.allocateInsteadAndReset(n) == NULL) {
*status = U_MEMORY_ALLOCATION_ERROR;
return -1;
}
for(i=0;i<n;i++) {
#if defined(ULOC_DEBUG)
/*fprintf(stderr,"%d: s <%s> 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<char **>(uprv_malloc((size_t)(sizeof(fallbackList[0])*acceptListCount)));
if(fallbackList==NULL) {
*status = U_MEMORY_ALLOCATION_ERROR;
return -1;
}
for(i=0;i<acceptListCount;i++) {
#if defined(ULOC_DEBUG)
fprintf(stderr,"%02d: %s\n", i, acceptList[i]);
#endif
while((l=uenum_next(availableLocales, NULL, status)) != NULL) {
#if defined(ULOC_DEBUG)
fprintf(stderr," %s\n", l);
#endif
len = (int32_t)uprv_strlen(l);
if(!uprv_strcmp(acceptList[i], l)) {
if(outResult) {
*outResult = ULOC_ACCEPT_VALID;
}
#if defined(ULOC_DEBUG)
fprintf(stderr, "MATCH! %s\n", l);
#endif
if(len>0) {
uprv_strncpy(result, l, uprv_min(len, resultAvailable));
}
for(j=0;j<i;j++) {
uprv_free(fallbackList[j]);
}
uprv_free(fallbackList);
return u_terminateChars(result, resultAvailable, len, status);
}
if(len>maxLen) {
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;i<acceptListCount;i++) {
if(fallbackList[i] && ((int32_t)uprv_strlen(fallbackList[i])==maxLen)) {
#if defined(ULOC_DEBUG)
fprintf(stderr,"Try: [%s]", fallbackList[i]);
#endif
while((l=uenum_next(availableLocales, NULL, status)) != NULL) {
#if defined(ULOC_DEBUG)
fprintf(stderr," %s\n", l);
#endif
len = (int32_t)uprv_strlen(l);
if(!uprv_strcmp(fallbackList[i], l)) {
if(outResult) {
*outResult = ULOC_ACCEPT_FALLBACK;
}
#if defined(ULOC_DEBUG)
fprintf(stderr, "fallback MATCH! %s\n", l);
#endif
if(len>0) {
uprv_strncpy(result, l, uprv_min(len, resultAvailable));
}
for(j=0;j<acceptListCount;j++) {
uprv_free(fallbackList[j]);
}
uprv_free(fallbackList);
return u_terminateChars(result, resultAvailable, len, status);
}
}
uenum_reset(availableLocales, status);
if(uloc_getParent(fallbackList[i], tmp, UPRV_LENGTHOF(tmp), status)!=0) {
uprv_free(fallbackList[i]);
fallbackList[i] = uprv_strdup(tmp);
} else {
uprv_free(fallbackList[i]);
fallbackList[i]=0;
}
}
}
if(outResult) {
*outResult = ULOC_ACCEPT_FAILED;
}
}
for(i=0;i<acceptListCount;i++) {
uprv_free(fallbackList[i]);
}
uprv_free(fallbackList);
return -1;
}
U_CAPI const char* U_EXPORT2
uloc_toUnicodeLocaleKey(const char* keyword)
{

View File

@ -2988,30 +2988,33 @@ static void TestAcceptLanguage(void) {
} tests[] = {
/*0*/{ 0, NULL, "mt_MT", ULOC_ACCEPT_VALID, U_ZERO_ERROR},
/*1*/{ 1, NULL, "en", ULOC_ACCEPT_VALID, U_ZERO_ERROR},
/*2*/{ 2, NULL, "en", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR},
/*2*/{ 2, NULL, "en_GB", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR},
/*3*/{ 3, NULL, "", ULOC_ACCEPT_FAILED, U_ZERO_ERROR},
/*4*/{ 4, NULL, "es", ULOC_ACCEPT_VALID, U_ZERO_ERROR},
/*5*/{ 5, NULL, "en", ULOC_ACCEPT_VALID, U_ZERO_ERROR}, /* XF */
/*5*/{ 5, NULL, "zh", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR}, /* XF */
/*6*/{ 6, NULL, "ja", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR}, /* XF */
/*7*/{ 7, NULL, "zh", ULOC_ACCEPT_FALLBACK, U_ZERO_ERROR}, /* XF */
/*8*/{ 8, NULL, "", ULOC_ACCEPT_FAILED, U_ZERO_ERROR }, /* */
/*9*/{ 9, NULL, "", ULOC_ACCEPT_FAILED, U_ZERO_ERROR }, /* */
/*10*/{10, NULL, "", ULOC_ACCEPT_FAILED, U_BUFFER_OVERFLOW_ERROR }, /* */
/*11*/{11, NULL, "", ULOC_ACCEPT_FAILED, U_BUFFER_OVERFLOW_ERROR }, /* */
/*8*/{ 8, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
/*9*/{ 9, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
/*10*/{10, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
/*11*/{11, NULL, "", ULOC_ACCEPT_FAILED, U_ILLEGAL_ARGUMENT_ERROR }, /* */
};
const int32_t numTests = UPRV_LENGTHOF(tests);
static const char *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",
/*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=0.01",
/*1*/ "ja;q=0.5, en;q=0.8, tlh",
/*2*/ "en-wf, de-lx;q=0.8",
/*3*/ "mga-ie;q=0.9, tlh",
/*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, 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, xx-yy;q=.1, "
/*3*/ "mga-ie;q=0.9, sux",
/*4*/ "xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, "
"xxx-yyy;q=0.01, xxx-yyy;q=0.01, xxx-yyy;q=0.01, xx-yy;q=0.1, "
"es",
/*5*/ "zh-xx;q=0.9, en;q=0.6",
/*6*/ "ja-JA",
@ -3042,12 +3045,17 @@ static void TestAcceptLanguage(void) {
available = ures_openAvailableLocales(tests[i].icuSet, &status);
tmp[0]=0;
rc = uloc_acceptLanguageFromHTTP(tmp, 199, &outResult, http[tests[i].httpSet], available, &status);
rc = uloc_acceptLanguageFromHTTP(tmp, 199, &outResult,
http[tests[i].httpSet], available, &status);
(void)rc; /* Suppress set but not used warning. */
uenum_close(available);
log_verbose(" got %s, %s [%s]\n", tmp[0]?tmp:"(EMPTY)", acceptResult(outResult), u_errorName(status));
log_verbose(" got %s, %s [%s]\n",
tmp[0]?tmp:"(EMPTY)", acceptResult(outResult), u_errorName(status));
if(status != tests[i].expectStatus) {
log_err_status(status, "FAIL: expected status %s but got %s\n", u_errorName(tests[i].expectStatus), u_errorName(status));
log_err_status(status,
"FAIL: expected status %s but got %s\n",
u_errorName(tests[i].expectStatus),
u_errorName(status));
} else if(U_SUCCESS(tests[i].expectStatus)) {
/* don't check content if expected failure */
if(outResult != tests[i].res) {
@ -3055,10 +3063,13 @@ static void TestAcceptLanguage(void) {
acceptResult( tests[i].res),
acceptResult( outResult));
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));
i, http[tests[i].httpSet], tests[i].icuSet,
tests[i].expect,acceptResult(tests[i].res));
}
if((outResult>0)&&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));
}

View File

@ -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<GlobalizationPreferen
if (isFrozen()) {
throw new UnsupportedOperationException("Attempt to modify immutable object");
}
ULocale[] acceptLocales = null;
try {
acceptLocales = ULocale.parseAcceptLanguage(acceptLanguageString, true);
} catch (ParseException pe) {
//TODO: revisit after 3.8
throw new IllegalArgumentException("Invalid Accept-Language string");
}
return setLocales(acceptLocales);
Set<ULocale> 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<GlobalizationPreferen
* @provisional This API might change or be removed in a future release.
*/
protected List<ULocale> processLocales(List<ULocale> 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<ULocale> result = new ArrayList<>();
/*
* Step 1: Relocate later occurrence of more specific locale
@ -824,9 +823,7 @@ public class GlobalizationPreferences implements Freezable<GlobalizationPreferen
* Before - en_US, fr_FR, zh, en_US_Boston, zh_TW, zh_Hant, fr_CA
* After - en_US_Boston, en_US, fr_FR, zh_TW, zh_Hant, zh, fr_CA
*/
for (int i = 0; i < inputLocales.size(); i++) {
ULocale uloc = inputLocales.get(i);
for (ULocale uloc : inputLocales) {
String language = uloc.getLanguage();
String script = uloc.getScript();
String country = uloc.getCountry();

View File

@ -12,7 +12,6 @@ package com.ibm.icu.util;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -34,7 +33,6 @@ import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.impl.ICUResourceTableAccess;
import com.ibm.icu.impl.LocaleIDParser;
import com.ibm.icu.impl.LocaleIDs;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.impl.locale.AsciiUtil;
import com.ibm.icu.impl.locale.BaseLocale;
@ -417,15 +415,6 @@ public final class ULocale implements Serializable, Comparable<ULocale> {
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<ULocale> {
* 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.
*
* <p>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<ULocale> {
* 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.
*
* <p>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<acceptLanguageList.length;i++) {
ULocale aLocale = acceptLanguageList[i];
boolean[] setFallback = fallback;
do {
for(j=0;j<availableLocales.length;j++) {
if(availableLocales[j].equals(aLocale)) {
if(setFallback != null) {
setFallback[0]=false; // first time with this locale - not a fallback.
}
return availableLocales[j];
}
// compare to scriptless alias, so locales such as
// zh_TW, zh_CN are considered as available locales - see #7190
if (aLocale.getScript().length() == 0
&& availableLocales[j].getScript().length() > 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<ULocale> {
* 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.
*
* <p>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<ULocale> {
* 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.
*
* <p>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<ULocaleAcceptLanguageQ> {
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<ULocaleAcceptLanguageQ, ULocale> 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<ULocale> {
List<String>subtags = tag.getVariants();
// ICU-20478: Sort variants per UTS35.
ArrayList<String> variants = new ArrayList<String>(subtags);
ArrayList<String> variants = new ArrayList<>(subtags);
Collections.sort(variants);
for (String s : variants) {
buf.append(LanguageTag.SEP);

View File

@ -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<ULocale> resultLocales = gp.getLocales();
List<ULocale> 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

View File

@ -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<l;n++) {
int itemEnd = acceptLanguageList.indexOf(',',n);
if(itemEnd == -1) {
itemEnd = l;
}
int paramEnd = acceptLanguageList.indexOf(';',n);
double q = 1.0;
if((paramEnd != -1) && (paramEnd < itemEnd)) {
/* semicolon (;) is closer than end (,) */
int t = paramEnd + 1;
while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
t++;
}
if(acceptLanguageList.charAt(t)=='q') {
t++;
}
while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
t++;
}
if(acceptLanguageList.charAt(t)=='=') {
t++;
}
while(UCharacter.isWhitespace(acceptLanguageList.charAt(t))) {
t++;
}
try {
String val = acceptLanguageList.substring(t,itemEnd).trim();
q = Double.parseDouble(val);
} catch (NumberFormatException nfe) {
q = 1.0;
}
} else {
q = 1.0; //default
paramEnd = itemEnd;
}
String loc = acceptLanguageList.substring(n,paramEnd).trim();
int serial = map.size();
ULocaleAcceptLanguageQ entry = new ULocaleAcceptLanguageQ(q,serial);
map.put(entry, new ULocale(ULocale.canonicalize(loc))); // sort in reverse order.. 1.0, 0.9, 0.8 .. etc
n = itemEnd; // get next item. (n++ will skip over delimiter)
}
// 2. pull out the map
ULocale acceptList[] = (ULocale[])map.values().toArray(new ULocale[map.size()]);
return acceptList;
}
@Test
public void TestAcceptLanguage2() {
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 + ")");
boolean r[] = { false };
ULocale n = ULocale.acceptLanguage(StringToULocaleArray(ACCEPT_LANGUAGE_HTTP[i]), r);
Set<ULocale> 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);
}
}
}