ICU-6635 Updated the implementation of BCP47 language tag support, locale builder, etc to match the latest JDK locale enhancement proposal.

X-SVN-Rev: 26804
This commit is contained in:
Yoshito Umaoka 2009-10-23 22:01:51 +00:00
parent e698d415eb
commit e5dc053c11
12 changed files with 1968 additions and 1394 deletions

View File

@ -86,6 +86,9 @@ public final class AsciiUtil {
}
public static String toTitleString(String s) {
if (s.length() == 0) {
return s;
}
int idx = 0;
char c = s.charAt(idx);
if (!(c >= 'a' && c <= 'z')) {

View File

@ -8,64 +8,60 @@
package com.ibm.icu.impl.locale;
public final class BaseLocale {
private static final boolean JDKIMPL = false;
private String _language = "";
private String _script = "";
private String _region = "";
private String _variant = "";
private transient String _id = "";
private transient String _java6string = "";
private transient BaseLocale _parent;
private static final LocaleObjectCache<Key, BaseLocale> BASELOCALE_CACHE
= new LocaleObjectCache<Key, BaseLocale>();
private static final char SEPCHAR = '_';
private static final LocaleObjectCache<BaseLocaleKey,BaseLocale> BASELOCALECACHE
= new LocaleObjectCache<BaseLocaleKey,BaseLocale>();
public static final BaseLocale ROOT = new BaseLocale("", "", "", "");
public static final BaseLocale ROOT = BaseLocale.getInstance("", "", "", "");
private BaseLocale(String language, String script, String region, String variant) {
if (language != null) {
_language = language;
_language = AsciiUtil.toLowerString(language).intern();
}
if (script != null) {
_script = script;
_script = AsciiUtil.toTitleString(script).intern();
}
if (region != null) {
_region = region;
_region = AsciiUtil.toUpperString(region).intern();
}
if (variant != null) {
_variant = variant;
if (JDKIMPL) {
// preserve upper/lower cases
_variant = variant.intern();
} else {
_variant = AsciiUtil.toUpperString(variant).intern();
}
}
}
public static BaseLocale getInstance(String language, String script, String region, String variant) {
BaseLocaleKey key = new BaseLocaleKey(language, script, region, variant);
BaseLocale baseLocale = BASELOCALECACHE.get(key);
if (JDKIMPL) {
// JDK uses deprecated ISO639.1 language codes for he, yi and id
if (AsciiUtil.caseIgnoreMatch(language, "he")) {
language = "iw";
} else if (AsciiUtil.caseIgnoreMatch(language, "yi")) {
language = "ji";
} else if (AsciiUtil.caseIgnoreMatch(language, "id")) {
language = "in";
}
}
Key key = new Key(language, script, region, variant);
BaseLocale baseLocale = BASELOCALE_CACHE.get(key);
if (baseLocale == null) {
// Create a canonical BaseLocale instance
baseLocale = new BaseLocale(language, script, region, variant).canonicalize();
BASELOCALECACHE.put(baseLocale.createKey(), baseLocale);
baseLocale = new BaseLocale(language, script, region, variant);
BASELOCALE_CACHE.put(baseLocale.createKey(), baseLocale);
}
return baseLocale;
}
public boolean equals(Object obj) {
return (this == obj) ||
((obj instanceof BaseLocale) && _id == (((BaseLocale)obj)._id));
}
public int hashCode() {
return _id.hashCode();
}
public String getJava6String() {
return _java6string;
}
public String getLanguage() {
return _language;
}
@ -82,116 +78,41 @@ public final class BaseLocale {
return _variant;
}
public BaseLocale getParent() {
return _parent;
}
public String getID() {
return _id;
}
public String toString() {
return _id;
StringBuilder buf = new StringBuilder();
if (_language.length() > 0) {
buf.append("language=");
buf.append(_language);
}
if (_script.length() > 0) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append("script=");
buf.append(_script);
}
if (_region.length() > 0) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append("region=");
buf.append(_region);
}
if (_variant.length() > 0) {
if (buf.length() > 0) {
buf.append(", ");
}
buf.append("variant=");
buf.append(_variant);
}
return buf.toString();
}
private BaseLocale canonicalize() {
StringBuilder id = new StringBuilder();
int languageLen = _language.length();
int scriptLen = _script.length();
int regionLen = _region.length();
int variantLen = _variant.length();
if (languageLen > 0) {
// language to lower case
_language = AsciiUtil.toLowerString(_language).intern();
id.append(_language);
}
if (scriptLen > 0) {
// script - the first letter to upper case, the rest to lower case
_script = AsciiUtil.toTitleString(_script).intern();
id.append(SEPCHAR);
id.append(_script);
}
if (regionLen > 0) {
// region to upper case
_region = AsciiUtil.toUpperString(_region).intern();
id.append(SEPCHAR);
id.append(_region);
}
if (variantLen > 0) {
// variant in JDK conventionally use upper case letters,
// but it was actually case sensitive. We normalize
// variant to upper case here, which might be breaking
// change. But at least it works well for all variants
// defined by JDK.
_variant = AsciiUtil.toUpperString(_variant).intern();
if (regionLen == 0) {
id.append(SEPCHAR);
}
id.append(SEPCHAR);
id.append(_variant);
}
_id = id.toString().intern();
// Compose legacy JDK ID string if required
if (languageLen == 0 && regionLen == 0 && variantLen > 0) {
_java6string = "";
} else if (scriptLen > 0 || (regionLen == 0 && variantLen > 0)) {
StringBuilder buf = new StringBuilder(_language);
if (regionLen > 0) {
buf.append(SEPCHAR);
buf.append(_region);
} else if (variantLen > 0) {
buf.append(SEPCHAR);
}
if (variantLen > 0) {
buf.append(SEPCHAR);
buf.append(_variant);
}
_java6string = buf.toString().intern();
} else {
_java6string = _id;
}
// Resolve parent
if (variantLen > 0) {
// variant field in Java Locale may contain multiple
// subtags
int lastSep = _variant.lastIndexOf(SEPCHAR);
if (lastSep == -1) {
_parent = getInstance(_language, _script, _region, "");
} else {
_parent = getInstance(_language, _script, _region, _variant.substring(0, lastSep));
}
} else if (regionLen > 0) {
_parent = getInstance(_language, _script, "", "");
} else if (scriptLen > 0) {
_parent = getInstance(_language, "", "", "");
} else if (languageLen > 0) {
_parent = ROOT;
} else {
// This is the root
// We should never get here, because ROOT is pre-populated.
_parent = null;
}
return this;
private Key createKey() {
return new Key(_language, _script, _region, _variant);
}
private BaseLocaleKey createKey() {
return new BaseLocaleKey(_language, _script, _region, _variant);
}
public static class BaseLocaleKey implements Comparable<BaseLocaleKey> {
private static class Key implements Comparable<Key> {
private String _lang = "";
private String _scrt = "";
private String _regn = "";
@ -199,7 +120,7 @@ public final class BaseLocale {
private int _hash; // Default to 0
public BaseLocaleKey(String language, String script, String region, String variant) {
public Key(String language, String script, String region, String variant) {
if (language != null) {
_lang = language;
}
@ -215,22 +136,34 @@ public final class BaseLocale {
}
public boolean equals(Object obj) {
if (JDKIMPL) {
return (this == obj) ||
(obj instanceof Key)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._lang, this._lang)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._scrt, this._scrt)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._regn, this._regn)
&& ((Key)obj)._vart.equals(_vart); // variant is case sensitive in JDK!
}
return (this == obj) ||
(obj instanceof BaseLocaleKey)
&& AsciiUtil.caseIgnoreMatch(((BaseLocaleKey)obj)._lang, this._lang)
&& AsciiUtil.caseIgnoreMatch(((BaseLocaleKey)obj)._scrt, this._scrt)
&& AsciiUtil.caseIgnoreMatch(((BaseLocaleKey)obj)._regn, this._regn)
&& ((BaseLocaleKey)obj)._vart.equals(_vart); // variant is case sensitive in JDK!
(obj instanceof Key)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._lang, this._lang)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._scrt, this._scrt)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._regn, this._regn)
&& AsciiUtil.caseIgnoreMatch(((Key)obj)._vart, this._vart);
}
public int compareTo(BaseLocaleKey other) {
public int compareTo(Key other) {
int res = AsciiUtil.caseIgnoreCompare(this._lang, other._lang);
if (res == 0) {
res = AsciiUtil.caseIgnoreCompare(this._scrt, other._scrt);
if (res == 0) {
res = AsciiUtil.caseIgnoreCompare(this._regn, other._regn);
if (res == 0) {
res = AsciiUtil.caseIgnoreCompare(this._vart, other._vart);
if (JDKIMPL) {
res = this._vart.compareTo(other._vart);
} else {
res = AsciiUtil.caseIgnoreCompare(this._vart, other._vart);
}
}
}
}
@ -251,7 +184,11 @@ public final class BaseLocale {
h = 31*h + AsciiUtil.toLower(_regn.charAt(i));
}
for (int i = 0; i < _vart.length(); i++) {
h = 31*h + AsciiUtil.toLower(_vart.charAt(i));
if (JDKIMPL) {
h = 31*h + _vart.charAt(i);
} else {
h = 31*h + AsciiUtil.toLower(_vart.charAt(i));
}
}
_hash = h;
}

View File

@ -0,0 +1,107 @@
/*
*******************************************************************************
* Copyright (C) 2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl.locale;
import com.ibm.icu.impl.locale.LanguageTag.ParseStatus;
public class Extension {
private char _key;
protected String _value;
protected Extension(char key) {
_key = key;
}
public char getKey() {
return _key;
}
public String getValue() {
return _value;
}
public static Extension create(StringTokenIterator itr, ParseStatus sts) {
if (sts.isError() || itr.isDone()) {
return null;
}
Extension ext = null;
String key = itr.current();
if (LanguageTag.isExtensionSingleton(key) || LanguageTag.isPrivateuseSingleton(key)) {
itr.next();
ext = create(key.charAt(0), itr, sts);
}
return ext;
}
public static Extension create(char key, StringTokenIterator val, ParseStatus sts) {
if (sts.isError()) {
return null;
}
if (val.isDone()) {
sts.errorIndex = val.currentStart();
sts.errorMsg = "Missing extension subtag for extension :" + key;
return null;
}
Extension ext = null;
key = AsciiUtil.toLower(key);
switch (key) {
case UnicodeLocaleExtension.SINGLETON:
ext = new UnicodeLocaleExtension();
break;
case PrivateuseExtension.SINGLETON:
ext = new PrivateuseExtension();
break;
default:
ext = new Extension(key);
break;
}
ext.setExtensionValue(val, sts);
if (ext.getValue() == null) {
// return null only when nothing parsed.
return null;
}
return ext;
}
protected void setExtensionValue(StringTokenIterator itr, ParseStatus sts) {
if (sts.isError() || itr.isDone()) {
_value = null;
return;
}
StringBuilder buf = new StringBuilder();
while (!itr.isDone()) {
String s = itr.current();
if (!LanguageTag.isExtensionSubtag(s)) {
break;
}
s = LanguageTag.canonicalizeExtensionSubtag(s);
if (buf.length() != 0) {
buf.append(LanguageTag.SEP);
}
buf.append(s);
sts.parseLength = itr.currentEnd();
itr.next();
}
if (buf.length() == 0) {
sts.errorIndex = itr.currentStart();
sts.errorMsg = "Invalid extension subtag: " + itr.current();
_value = null;
} else {
_value = buf.toString();
}
}
}

View File

@ -6,46 +6,44 @@
*/
package com.ibm.icu.impl.locale;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public final class InternalLocaleBuilder {
import com.ibm.icu.impl.locale.LanguageTag.ParseStatus;
public static final char PRIVATEUSEKEY = 'x';
public final class InternalLocaleBuilder {
private String _language = "";
private String _script = "";
private String _region = "";
private String _variant = "";
private SortedMap<Character, Extension> _extMap;
private FieldHandler _handler = FieldHandler.DEFAULT;
private final boolean _lenientVariant;
private HashMap<Character, String> _extMap;
private HashMap<String, String> _kwdMap;
private static final char LDMLSINGLETON = 'u';
private static final String LANGTAGSEP = "-";
private static final String LOCALESEP = "_";
private static final int DEFAULTMAPCAPACITY = 4;
public InternalLocaleBuilder() {
this(false);
}
public InternalLocaleBuilder(FieldHandler handler) {
_handler = handler;
public InternalLocaleBuilder(boolean lenientVariant) {
_lenientVariant = lenientVariant;
}
public boolean isLenientVariant() {
return _lenientVariant;
}
public InternalLocaleBuilder setLanguage(String language) throws LocaleSyntaxException {
String newval = "";
if (language.length() > 0) {
newval = _handler.process(FieldType.LANGUAGE, language);
if (newval == null) {
throw new LocaleSyntaxException("Ill-formed language: " + language);
if (!LanguageTag.isLanguage(language)) {
throw new LocaleSyntaxException("Ill-formed language: " + language, 0);
}
newval = LanguageTag.canonicalizeLanguage(language);
}
_language = newval;
return this;
@ -54,10 +52,10 @@ public final class InternalLocaleBuilder {
public InternalLocaleBuilder setScript(String script) throws LocaleSyntaxException {
String newval = "";
if (script.length() > 0) {
newval = _handler.process(FieldType.SCRIPT, script);
if (newval == null) {
throw new LocaleSyntaxException("Ill-formed script: " + script);
if (!LanguageTag.isScript(script)) {
throw new LocaleSyntaxException("Ill-formed script: " + script, 0);
}
newval = LanguageTag.canonicalizeScript(script);
}
_script = newval;
return this;
@ -66,10 +64,10 @@ public final class InternalLocaleBuilder {
public InternalLocaleBuilder setRegion(String region) throws LocaleSyntaxException {
String newval = "";
if (region.length() > 0) {
newval = _handler.process(FieldType.REGION, region);
if (newval == null) {
if (!LanguageTag.isRegion(region)) {
throw new LocaleSyntaxException("Ill-formed region: " + region);
}
newval = LanguageTag.canonicalizeRegion(region);
}
_region = newval;
return this;
@ -78,96 +76,160 @@ public final class InternalLocaleBuilder {
public InternalLocaleBuilder setVariant(String variant) throws LocaleSyntaxException {
String newval = "";
if (variant.length() > 0) {
newval = _handler.process(FieldType.VARIANT, variant);
if (newval == null) {
throw new LocaleSyntaxException("Ill-formed variant: " + variant);
if (_lenientVariant) {
newval = variant;
} else {
newval = processVariant(variant);
}
}
_variant = newval;
return this;
}
public InternalLocaleBuilder setLDMLExtensionValue(String key, String type) throws LocaleSyntaxException {
public InternalLocaleBuilder setUnicodeLocaleExtension(String key, String type) throws LocaleSyntaxException {
if (key.length() == 0) {
throw new LocaleSyntaxException("Empty LDML extension key");
throw new LocaleSyntaxException("Empty Unicode locale extension key");
}
String kwdkey = _handler.process(FieldType.LDMLKEY, key);
if (kwdkey == null) {
throw new LocaleSyntaxException("Ill-formed LDML extension key: " + key);
if (!UnicodeLocaleExtension.isKey(key)) {
throw new LocaleSyntaxException("Ill-formed Unicode locale extension key: " + key, 0);
}
key = UnicodeLocaleExtension.canonicalizeKey(key);
UnicodeLocaleExtension ulext = null;
if (_extMap != null) {
ulext = (UnicodeLocaleExtension)_extMap.get(Character.valueOf(UnicodeLocaleExtension.SINGLETON));
}
if (type.length() == 0) {
if (_kwdMap != null) {
_kwdMap.remove(kwdkey);
if (ulext != null) {
ulext.remove(key);
}
} else {
String kwdtype = _handler.process(FieldType.LDMLTYPE, type);
if (kwdtype == null) {
throw new LocaleSyntaxException("Ill-formed LDML extension value: " + type);
StringBuilder buf = new StringBuilder();
StringTokenIterator sti = new StringTokenIterator(type, LanguageTag.SEP);
for (String subtag = sti.first(); !sti.isDone(); subtag = sti.next()) {
if (!UnicodeLocaleExtension.isTypeSubtag(subtag)) {
throw new LocaleSyntaxException("Ill-formed Unicode locale extension type: " + type, sti.currentStart());
}
if (buf.length() > 0) {
buf.append(LanguageTag.SEP);
}
buf.append(UnicodeLocaleExtension.canonicalizeTypeSubtag(subtag));
}
if (_kwdMap == null) {
_kwdMap = new HashMap<String, String>(DEFAULTMAPCAPACITY);
if (ulext == null) {
SortedMap<String, String> ktmap = new TreeMap<String, String>();
ktmap.put(key, buf.toString());
ulext = new UnicodeLocaleExtension(ktmap);
if (_extMap == null) {
_extMap = new TreeMap<Character, Extension>();
}
_extMap.put(Character.valueOf(UnicodeLocaleExtension.SINGLETON), ulext);
} else {
ulext.put(key, buf.toString());
}
_kwdMap.put(kwdkey, kwdtype);
}
return this;
}
public InternalLocaleBuilder setExtension(char singleton, String value) throws LocaleSyntaxException {
if (!LocaleExtensions.isValidExtensionKey(singleton)) {
String strSingleton = String.valueOf(singleton);
if (!LanguageTag.isExtensionSingleton(strSingleton) && !LanguageTag.isPrivateuseSingleton(strSingleton)) {
throw new LocaleSyntaxException("Ill-formed extension key: " + singleton);
}
// singleton char to lower case
singleton = AsciiUtil.toLower(singleton);
strSingleton = LanguageTag.canonicalizeExtensionSingleton(strSingleton);
Character key = Character.valueOf(strSingleton.charAt(0));
if (singleton == LDMLSINGLETON) {
// 'u' extension reserved for locale keywords
if (_kwdMap != null) {
// blow out the keywords currently set
_kwdMap.clear();
}
// parse locale keyword extension subtags
String[] kwdtags = (value.replaceAll(LOCALESEP, LANGTAGSEP)).split(LANGTAGSEP);
if ((kwdtags.length % 2) != 0) {
// number of keyword subtags must be even number
throw new LocaleSyntaxException("Ill-formed LDML extension key/value pairs: " + value);
}
int idx = 0;
while (idx < kwdtags.length) {
String kwdkey = _handler.process(FieldType.LDMLKEY, kwdtags[idx++]);
String kwdtype = _handler.process(FieldType.LDMLTYPE, kwdtags[idx++]);
if (kwdkey == null || kwdkey.length() == 0
|| kwdtype == null || kwdtype.length() == 0) {
throw new LocaleSyntaxException("Ill-formed LDML extension key/value pairs: " + value);
}
if (_kwdMap == null) {
_kwdMap = new HashMap<String, String>(kwdtags.length / 2);
}
String prevVal = _kwdMap.put(kwdkey, kwdtype);
if (prevVal != null) {
throw new LocaleSyntaxException("Ill-formed LDML extension containing duplicated keys: " + value);
}
if (value.length() == 0) {
if (_extMap != null) {
_extMap.remove(key);
}
} else {
// other extensions including privateuse
if (value.length() == 0) {
if (_extMap != null) {
_extMap.remove(Character.valueOf(singleton));
}
} else {
FieldType ftype = (singleton == PRIVATEUSEKEY) ? FieldType.PRIVATEUSE : FieldType.EXTENSION;
String extval = _handler.process(ftype, value);
if (extval == null) {
throw new LocaleSyntaxException("Ill-formed LDML extension value: " + value);
}
if (_extMap == null) {
_extMap = new HashMap<Character, String>(DEFAULTMAPCAPACITY);
}
_extMap.put(Character.valueOf(singleton), extval);
StringTokenIterator sti = new StringTokenIterator(value, LanguageTag.SEP);
ParseStatus sts = new ParseStatus();
Extension ext = Extension.create(key.charValue(), sti, sts);
if (sts.isError()) {
throw new LocaleSyntaxException(sts.errorMsg, sts.errorIndex);
}
if (sts.parseLength != value.length() || ext == null) {
throw new LocaleSyntaxException("Ill-formed extension value: " + value, sti.currentStart());
}
if (_extMap == null) {
_extMap = new TreeMap<Character, Extension>();
}
_extMap.put(key, ext);
}
return this;
}
public InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions extensions) throws LocaleSyntaxException {
String language = base.getLanguage();
String script = base.getScript();
String region = base.getRegion();
String variant = base.getVariant();
// Validate base locale fields before updating internal state.
// LocaleExtensions always store validated/canonicalized values,
// so no checks are necessary.
if (language.length() > 0) {
if (!LanguageTag.isLanguage(language)) {
throw new LocaleSyntaxException("Ill-formed language: " + language);
}
language = LanguageTag.canonicalizeLanguage(language);
}
if (script.length() > 0) {
if (!LanguageTag.isScript(script)) {
throw new LocaleSyntaxException("Ill-formed script: " + script);
}
script = LanguageTag.canonicalizeScript(script);
}
if (region.length() > 0) {
if (!LanguageTag.isRegion(region)) {
throw new LocaleSyntaxException("Ill-formed region: " + region);
}
region = LanguageTag.canonicalizeRegion(region);
}
if (_lenientVariant) {
// In lenient variant mode, parse special private use value
// reserved for Java Locale.
String privuse = extensions.getExtensionValue(Character.valueOf(LanguageTag.PRIVATEUSE.charAt(0)));
if (privuse != null) {
variant = LanguageTag.getJavaCompatibleVariant(variant, privuse);
}
} else {
if (variant.length() > 0) {
variant = processVariant(variant);
}
}
// update builder's internal fields
_language = language;
_script = script;
_region = region;
_variant = variant;
// empty extensions
if (_extMap == null) {
_extMap = new TreeMap<Character, Extension>();
} else {
_extMap.clear();
}
Set<Character> extKeys = extensions.getKeys();
for (Character key : extKeys) {
Extension ext = extensions.getExtension(key);
if (_lenientVariant && (ext instanceof PrivateuseExtension)) {
String modPrivuse = LanguageTag.getJavaCompatiblePrivateuse(ext.getValue());
if (!modPrivuse.equals(ext.getValue())) {
ext = new PrivateuseExtension(modPrivuse);
}
}
_extMap.put(key, ext);
}
return this;
}
@ -184,9 +246,6 @@ public final class InternalLocaleBuilder {
if (_extMap != null) {
_extMap.clear();
}
if (_kwdMap != null) {
_kwdMap.clear();
}
return this;
}
@ -195,155 +254,28 @@ public final class InternalLocaleBuilder {
}
public LocaleExtensions getLocaleExtensions() {
TreeMap<Character, String> extMap = null;
TreeMap<String, String> kwdMap = null;
// process keywords
if (_kwdMap != null && _kwdMap.size() > 0) {
Set<Map.Entry<String, String>> kwds = _kwdMap.entrySet();
for (Map.Entry<String, String> kwd : kwds) {
String key = kwd.getKey();
String type = kwd.getValue();
if (kwdMap == null) {
kwdMap = new TreeMap<String, String>();
}
kwdMap.put(key.intern(), type.intern());
}
if (_extMap != null && _extMap.size() > 0) {
return LocaleExtensions.getInstance(_extMap);
}
// process extensions and privateuse
if (_extMap != null) {
Set<Map.Entry<Character, String>> exts = _extMap.entrySet();
for (Map.Entry<Character, String> ext : exts) {
Character key = ext.getKey();
String value = ext.getValue();
if (extMap == null) {
extMap = new TreeMap<Character, String>();
}
extMap.put(key, value.intern());
}
}
// set canonical locale keyword extension string to the extension map
if (kwdMap != null) {
StringBuilder buf = new StringBuilder();
LocaleExtensions.keywordsToString(kwdMap, buf);
if (extMap == null) {
extMap = new TreeMap<Character, String>();
}
extMap.put(Character.valueOf(LDMLSINGLETON), buf.toString().intern());
}
return LocaleExtensions.getInstance(extMap, kwdMap);
return LocaleExtensions.EMPTY_EXTENSIONS;
}
protected enum FieldType {
LANGUAGE,
SCRIPT,
REGION,
VARIANT,
LDMLKEY,
LDMLTYPE,
EXTENSION,
PRIVATEUSE
}
private String processVariant(String variant) throws LocaleSyntaxException {
StringTokenIterator sti = new StringTokenIterator(variant, LOCALESEP);
ParseStatus sts = new ParseStatus();
public static class FieldHandler {
public static FieldHandler DEFAULT = new FieldHandler();
protected FieldHandler() {
List<String> variants = LanguageTag.DEFAULT_PARSER.parseVariants(sti, sts);
if (sts.parseLength != variant.length()) {
throw new LocaleSyntaxException("Ill-formed variant: " + variant, sti.currentStart());
}
public String process(FieldType type, String value) {
value = map(type, value);
if (value.length() > 0 && !validate(type, value)) {
return null;
StringBuilder buf = new StringBuilder();
for (String var : variants) {
if (buf.length() != 0) {
buf.append(LOCALESEP);
}
return value;
}
protected String map(FieldType type, String value) {
switch (type) {
case LANGUAGE:
value = AsciiUtil.toLowerString(value);
break;
case SCRIPT:
if (value.length() > 0) {
StringBuilder buf = new StringBuilder();
buf.append(AsciiUtil.toUpper(value.charAt(0)));
for (int i = 1; i < value.length(); i++) {
buf.append(AsciiUtil.toLower(value.charAt(i)));
}
value = buf.toString();
}
break;
case REGION:
value = AsciiUtil.toUpperString(value);
break;
case VARIANT:
// Java variant is case sensitive - so no case mapping here
value = value.replaceAll(LANGTAGSEP, LOCALESEP);
break;
case LDMLKEY:
case LDMLTYPE:
case EXTENSION:
case PRIVATEUSE:
value = AsciiUtil.toLowerString(value).replaceAll(LOCALESEP, LANGTAGSEP);
break;
}
return value;
}
protected boolean validate(FieldType type, String value) {
boolean isValid = false;
String[] subtags;
switch (type) {
case LANGUAGE:
isValid = LanguageTag.isLanguageSubtag(value);
break;
case SCRIPT:
isValid = LanguageTag.isScriptSubtag(value);
break;
case REGION:
isValid = LanguageTag.isRegionSubtag(value);
break;
case VARIANT:
// variant field could have multiple subtags
subtags = value.split(LOCALESEP);
for (String subtag : subtags) {
isValid = LanguageTag.isVariantSubtag(subtag);
if (!isValid) {
break;
}
}
break;
case LDMLKEY:
isValid = LocaleExtensions.isValidLDMLKey(value);
break;
case LDMLTYPE:
isValid = LocaleExtensions.isValidLDMLType(value);
break;
case EXTENSION:
subtags = value.split(LANGTAGSEP);
for (String subtag : subtags) {
isValid = LanguageTag.isExtensionSubtag(subtag);
if (!isValid) {
break;
}
}
break;
case PRIVATEUSE:
subtags = value.split(LANGTAGSEP);
for (String subtag : subtags) {
isValid = LanguageTag.isPrivateuseValueSubtag(subtag);
if (!isValid) {
break;
}
}
break;
}
return isValid;
buf.append(var);
}
return buf.toString();
}
}

View File

@ -7,260 +7,111 @@
package com.ibm.icu.impl.locale;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public final class LocaleExtensions {
public static final LocaleExtensions EMPTY_EXTENSIONS = new LocaleExtensions("");
private String _extensions;
private TreeMap<Character, String> _extMap;
private TreeMap<String, String> _kwdMap;
private static final String LOCALEEXTSEP = "-";
private static final String LDMLSINGLETON = "u";
private static final String PRIVUSE = "x";
private static final int MINLEN = 3; // minimum length of string representation "x-?"
import java.util.Map.Entry;
private LocaleExtensions(String extensions) {
_extensions = extensions == null ? "" : extensions;
public class LocaleExtensions {
private SortedMap<Character, Extension> _map = EMPTY_MAP;
private String _id = "";
private static final SortedMap<Character, Extension> EMPTY_MAP =
Collections.unmodifiableSortedMap(new TreeMap<Character, Extension>());
private static final LocaleObjectCache<String, LocaleExtensions> LOCALEEXTENSIONS_CACHE =
new LocaleObjectCache<String, LocaleExtensions>();
public static LocaleExtensions EMPTY_EXTENSIONS = new LocaleExtensions();
private LocaleExtensions() {
}
public static LocaleExtensions getInstance(String extensions) {
if (extensions == null || extensions.length() == 0) {
static LocaleExtensions getInstance(SortedMap<Character, Extension> map) {
if (map == null || map.isEmpty()) {
return EMPTY_EXTENSIONS;
}
extensions = AsciiUtil.toLowerString(extensions).replaceAll("_", LOCALEEXTSEP);
if (extensions.length() < MINLEN) {
// malformed extensions - too short
return new LocaleExtensions(extensions);
String id = getID(map);
LocaleExtensions exts = LOCALEEXTENSIONS_CACHE.get(id);
if (exts == null) {
exts = new LocaleExtensions();
exts._map = new TreeMap<Character, Extension>(map);
exts._id = id;
}
return exts;
}
TreeMap<Character, String> extMap = null;
TreeMap<String, String> kwdMap = null;
boolean bParseFailure = false;
// parse the extension subtags
String[] subtags = extensions.split(LOCALEEXTSEP);
String letter = null;
extMap = new TreeMap<Character, String>();
private static String getID(SortedMap<Character, Extension> map) {
StringBuilder buf = new StringBuilder();
boolean inLocaleKeywords = false;
boolean inPrivateUse = false;
String kwkey = null;
for (int i = 0; i < subtags.length; i++) {
if (subtags[i].length() == 0) {
// empty subtag
bParseFailure = true;
break;
}
if (subtags[i].length() == 1 && !inPrivateUse) {
if (letter != null) {
// next extension singleton
if (extMap.containsKey(subtags[i])) {
// duplicated singleton extension letter
bParseFailure = true;
break;
}
// write out the previous extension
if (inLocaleKeywords) {
if (kwkey != null) {
// no locale keyword key
bParseFailure = true;
break;
}
// creating a single string including locale keyword key/type pairs
keywordsToString(kwdMap, buf);
inLocaleKeywords = false;
}
if (buf.length() == 0) {
// empty subtag
bParseFailure = true;
break;
}
extMap.put(Character.valueOf(letter.charAt(0)), buf.toString().intern());
Extension privuse = null;
if (map != null && !map.isEmpty()) {
Set<Entry<Character, Extension>> entries = map.entrySet();
for (Entry<Character, Extension> entry : entries) {
Character key = entry.getKey();
if (key.charValue() == LanguageTag.PRIVATEUSE.charAt(0)) {
privuse = entry.getValue();
continue;
}
// preparation for next extension
if (subtags[i].equals(LDMLSINGLETON)) {
kwdMap = new TreeMap<String, String>();
inLocaleKeywords = true;
} else if (subtags[i].equals(PRIVUSE)) {
inPrivateUse = true;
}
buf.setLength(0);
letter = subtags[i];
continue;
}
if (inLocaleKeywords) {
if (kwkey == null) {
kwkey = subtags[i];
} else {
kwdMap.put(kwkey.intern(), subtags[i].intern());
kwkey = null;
}
} else {
// append an extension/prvate use subtag
if (buf.length() > 0) {
buf.append(LOCALEEXTSEP);
buf.append(LanguageTag.SEP);
}
buf.append(subtags[i]);
buf.append(entry.getKey());
buf.append(LanguageTag.SEP);
buf.append(entry.getValue().getValue());
}
}
if (!bParseFailure) {
// process the last extension
if (inLocaleKeywords) {
if (kwkey != null) {
bParseFailure = true;
} else {
// creating a single string including locale keyword key/type pairs
keywordsToString(kwdMap, buf);
}
}
if (buf.length() == 0) {
// empty subtag at the end
bParseFailure = true;
} else {
extMap.put(Character.valueOf(letter.charAt(0)), buf.toString().intern());
if (privuse != null) {
if (buf.length() > 0) {
buf.append(LanguageTag.SEP);
}
buf.append(LanguageTag.PRIVATEUSE);
buf.append(LanguageTag.SEP);
buf.append(privuse.getValue());
}
if (bParseFailure) {
// parsing the extension string failed.
// do not set any partial results in the result.
return new LocaleExtensions(extensions);
}
String canonical = extensionsToCanonicalString(extMap);
LocaleExtensions le = new LocaleExtensions(canonical);
le._extMap = extMap;
le._kwdMap = kwdMap;
return le;
return buf.toString();
}
// This method assumes extension map and locale keyword map
// are all in canonicalized format. This method is only used by
// InternalLocaleBuilder.
public static LocaleExtensions getInstance(TreeMap<Character, String> extMap, TreeMap<String ,String> kwdMap) {
if (extMap == null) {
return EMPTY_EXTENSIONS;
public Set<Character> getKeys() {
return Collections.unmodifiableSet(_map.keySet());
}
public Extension getExtension(Character key) {
return _map.get(key);
}
public String getExtensionValue(Character key) {
Extension ext = _map.get(key);
if (ext == null) {
return "";
}
String canonical = extensionsToCanonicalString(extMap);
LocaleExtensions le = new LocaleExtensions(canonical);
le._extMap = extMap;
le._kwdMap = kwdMap;
return le;
return ext.getValue();
}
public boolean equals(Object obj) {
return (this == obj) ||
((obj instanceof LocaleExtensions) && _extensions == (((LocaleExtensions)obj)._extensions));
}
public int hashCode() {
return _extensions.hashCode();
}
public Set<Character> getExtensionKeys() {
if (_extMap != null) {
return Collections.unmodifiableSet(_extMap.keySet());
public Set<String> getUnicodeLocaleKeys() {
Extension ext = _map.get(Character.valueOf(UnicodeLocaleExtension.SINGLETON));
if (ext == null) {
return Collections.emptySet();
}
return null;
assert (ext instanceof UnicodeLocaleExtension);
return ((UnicodeLocaleExtension)ext).getKeys();
}
public String getExtensionValue(char key) {
if (_extMap != null) {
return _extMap.get(Character.valueOf(key));
public String getUnicodeLocaleType(String unicodeLocaleKey) {
Extension ext = _map.get(Character.valueOf(UnicodeLocaleExtension.SINGLETON));
if (ext == null) {
return "";
}
return null;
}
public Set<String> getLDMLKeywordKeys() {
if (_kwdMap != null) {
return Collections.unmodifiableSet(_kwdMap.keySet());
}
return null;
}
public String getLDMLKeywordType(String key) {
if (key == null) {
throw new NullPointerException("LDML key must not be null");
}
if (_kwdMap != null) {
return _kwdMap.get(key);
}
return null;
}
public String getCanonicalString() {
return _extensions;
assert (ext instanceof UnicodeLocaleExtension);
return ((UnicodeLocaleExtension)ext).getType(unicodeLocaleKey);
}
public String toString() {
return _extensions;
return _id;
}
private static String extensionsToCanonicalString(TreeMap<Character, String> extMap) {
if (extMap == null || extMap.size() == 0) {
return "";
}
StringBuilder canonicalbuf = new StringBuilder();
String privUseStr = null;
if (extMap != null) {
Set<Map.Entry<Character, String>> entries = extMap.entrySet();
for (Map.Entry<Character, String> entry : entries) {
Character key = entry.getKey();
String value = entry.getValue();
if (key.charValue() == PRIVUSE.charAt(0)) {
privUseStr = value;
continue;
}
if (canonicalbuf.length() > 0) {
canonicalbuf.append(LOCALEEXTSEP);
}
canonicalbuf.append(key);
canonicalbuf.append(LOCALEEXTSEP);
canonicalbuf.append(value);
}
}
if (privUseStr != null) {
if (canonicalbuf.length() > 0) {
canonicalbuf.append(LOCALEEXTSEP);
}
canonicalbuf.append(PRIVUSE);
canonicalbuf.append(LOCALEEXTSEP);
canonicalbuf.append(privUseStr);
}
return canonicalbuf.toString().intern();
}
public static void keywordsToString(TreeMap<String, String> map, StringBuilder buf) {
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
if (buf.length() > 0) {
buf.append(LOCALEEXTSEP);
}
buf.append(entry.getKey());
buf.append(LOCALEEXTSEP);
buf.append(entry.getValue());
}
}
public static boolean isValidExtensionKey(char key) {
return AsciiUtil.isAlphaNumeric(key);
}
public static boolean isValidLDMLKey(String key) {
return (key.length() == 2) && AsciiUtil.isAlphaNumericString(key);
}
public static boolean isValidLDMLType(String type) {
return (type.length() >= 3) && (type.length() <= 8) && AsciiUtil.isAlphaNumericString(type);
public static boolean isValidKey(String key) {
return LanguageTag.isExtensionSingleton(key) || LanguageTag.isPrivateuseSingleton(key);
}
}

View File

@ -0,0 +1,53 @@
/*
*******************************************************************************
* Copyright (C) 2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl.locale;
import com.ibm.icu.impl.locale.LanguageTag.ParseStatus;
public class PrivateuseExtension extends Extension {
public static final char SINGLETON = 'x';
protected PrivateuseExtension() {
super(SINGLETON);
}
/*
* package local constructor only used by LanguageTag implementation
*/
PrivateuseExtension(String privuse) {
super(SINGLETON);
_value = privuse;
}
protected void setExtensionValue(StringTokenIterator itr, ParseStatus sts) {
if (sts.isError() || itr.isDone()) {
_value = null;
return;
}
StringBuilder buf = new StringBuilder();
while (!itr.isDone()) {
String s = itr.current();
if (!LanguageTag.isPrivateuseSubtag(s)) {
break;
}
s = LanguageTag.canonicalizePrivateuseSubtag(s);
if (buf.length() != 0) {
buf.append(LanguageTag.SEP);
}
buf.append(s);
sts.parseLength = itr.currentEnd();
itr.next();
}
if (buf.length() == 0) {
_value = null;
} else {
_value = buf.toString();
}
}
}

View File

@ -0,0 +1,93 @@
/*
*******************************************************************************
* Copyright (C) 2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl.locale;
public class StringTokenIterator {
private String _text;
private String _dlms;
private String _token;
private int _start;
private int _end;
private boolean _done;
public StringTokenIterator(String text, String dlms) {
_text = text;
_dlms = dlms;
setStart(0);
}
public String first() {
setStart(0);
return _token;
}
public String current() {
return _token;
}
public int currentStart() {
return _start;
}
public int currentEnd() {
return _end;
}
public boolean isDone() {
return _done;
}
public String next() {
if (hasNext()) {
_start = _end + 1;
_end = nextDelimiter(_start);
_token = _text.substring(_start, _end);
} else {
_start = _end;
_token = null;
_done = true;
}
return _token;
}
public boolean hasNext() {
return (_end < _text.length());
}
public StringTokenIterator setStart(int offset) {
if (offset >= _text.length()) {
throw new IndexOutOfBoundsException();
}
_start = offset;
_end = nextDelimiter(_start);
_token = _text.substring(_start, _end);
_done = false;
return this;
}
public StringTokenIterator setText(String text) {
_text = text;
setStart(0);
return this;
}
private int nextDelimiter(int start) {
int idx = start;
outer: while (idx < _text.length()) {
char c = _text.charAt(idx);
for (int i = 0; i < _dlms.length(); i++) {
if (c == _dlms.charAt(i)) {
break outer;
}
}
idx++;
}
return idx;
}
}

View File

@ -0,0 +1,193 @@
/*
*******************************************************************************
* Copyright (C) 2009, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.impl.locale;
import java.util.Collections;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;
import com.ibm.icu.impl.locale.LanguageTag.ParseStatus;
public class UnicodeLocaleExtension extends Extension {
public static final char SINGLETON = 'u';
private SortedMap<String, String> _keyTypeMap;
protected UnicodeLocaleExtension() {
super(SINGLETON);
}
/*
* Package local constructor only used by InternalLocaleBuilder
*/
UnicodeLocaleExtension(SortedMap<String, String> keyTypeMap) {
super(SINGLETON);
_keyTypeMap = keyTypeMap;
updateStringValue();
}
protected void setExtensionValue(StringTokenIterator itr, ParseStatus sts) {
if (sts.isError() || itr.isDone()) {
_value = null;
return;
}
SortedMap<String, String> keyTypeMap = new TreeMap<String, String>();
String ukey = null;
StringBuilder buf = new StringBuilder();
int typeEnd = -1;
while (!itr.isDone()) {
String s = itr.current();
if (isTypeSubtag(s)) {
if (ukey == null) {
// key is expected
sts.errorIndex = itr.currentStart();
sts.errorMsg = "Invalid Unicode locale extension key: " + s;
break;
}
if (buf.length() > 0) {
buf.append(LanguageTag.SEP);
}
buf.append(canonicalizeTypeSubtag(s));
typeEnd = itr.currentEnd();
if (!itr.hasNext()) {
// emit the last key/type
keyTypeMap.put(ukey, buf.toString());
sts.parseLength = typeEnd;
itr.next();
break;
}
} else {
// key or others
if (ukey != null) {
if (buf.length() > 0) {
// emit previous key and value
keyTypeMap.put(ukey, buf.toString());
sts.parseLength = typeEnd;
} else {
// type is expected
sts.errorIndex = itr.currentStart();
sts.errorMsg = "Invalid Unicode locale extension type: " + s;
break;
}
}
if (isKey(s)) {
if (itr.hasNext()) {
ukey = canonicalizeKey(s);
buf.setLength(0);
typeEnd = -1;
} else {
// missing type
sts.errorIndex = itr.currentStart();
sts.errorMsg = "Missing subtag for Unicode locale extension: " + s;
itr.next();
break;
}
} else {
// others
if (keyTypeMap.size() == 0) {
// key is expected
sts.errorIndex = itr.currentStart();
sts.errorMsg = "Invalid Unicode locale extension key: " + s;
}
break;
}
}
itr.next();
}
if (keyTypeMap.size() == 0) {
_value = null;
return;
}
_keyTypeMap = keyTypeMap;
updateStringValue();
}
public Set<String> getKeys() {
if (_keyTypeMap == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(_keyTypeMap.keySet());
}
public String getType(String key) {
String type = null;
if (_keyTypeMap != null) {
type = _keyTypeMap.get(key);
}
return (type == null ? "" : type);
}
public static boolean isKey(String s) {
// 2alphanum
return (s.length() == 2) && AsciiUtil.isAlphaNumericString(s);
}
public static boolean isTypeSubtag(String s) {
// 3*8alphanum
return (s.length() >= 3) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s);
}
public static String canonicalizeKey(String s) {
return LanguageTag.canonicalizeExtensionSubtag(s);
}
public static String canonicalizeTypeSubtag(String s) {
return LanguageTag.canonicalizeExtensionSubtag(s);
}
// These methods are only used by InterlaLocaleBuilder
UnicodeLocaleExtension remove(String key) {
if (_keyTypeMap != null) {
_keyTypeMap.remove(key);
updateStringValue();
}
return this;
}
UnicodeLocaleExtension put(String key, String type) {
if (_keyTypeMap != null) {
_keyTypeMap.put(key, type);
updateStringValue();
}
return this;
}
private void updateStringValue() {
_value = null;
if (_keyTypeMap != null) {
// re-construct string representation
StringBuilder valBuf = new StringBuilder();
Set<Entry<String, String>> entries = _keyTypeMap.entrySet();
boolean isFirst = true;
for (Entry<String, String> e : entries) {
if (isFirst) {
isFirst = false;
} else {
valBuf.append(LanguageTag.SEP);
}
valBuf.append(e.getKey());
valBuf.append(LanguageTag.SEP);
valBuf.append(e.getValue());
}
if (valBuf.length() > 0) {
_value = valBuf.toString();
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ public class LocaleBuilderTest extends TestFmwk {
}
public void TestLocaleBuilder() {
// First String "st": strict (default) / "lv": lenient variant
// "L": +1 = language
// "S": +1 = script
// "R": +1 = region
@ -33,31 +34,42 @@ public class LocaleBuilderTest extends TestFmwk {
// "X": indicates an exception must be thrown
// "T": +1 = expected language tag
String[][] TESTCASE = {
{"L", "en", "R", "us", "T", "en-US", "en_US"},
{"L", "en", "R", "FR", "L", "fr", "T", "fr-FR", "fr_FR"},
{"L", "123", "X"},
{"R", "us", "T", "und-US", "_US"},
{"R", "usa", "X"},
{"R", "123", "L", "en", "T", "en-123", "en_123"},
{"S", "LATN", "L", "DE", "T", "de-Latn", "de_Latn"},
{"S", "latin", "X"},
{"L", "th", "R", "th", "K", "nu", "thai", "T", "th-TH-u-nu-thai", "th_TH@numbers=thai"},
{"E", "z", "ExtZ", "L", "en", "T", "en-z-extz", "en@z=extz"},
{"L", "fr", "R", "FR", "P", "Yoshito-ICU", "T", "fr-FR-x-yoshito-icu", "fr_FR@x=yoshito-icu"},
{"L", "ja", "R", "jp", "K", "ca", "japanese", "T", "ja-JP-u-ca-japanese", "ja_JP@calendar=japanese"},
{"K", "co", "PHONEBK", "K", "ca", "gregory", "L", "De", "T", "de-u-ca-gregory-co-phonebk", "de@calendar=gregorian;collation=phonebook"},
{"E", "o", "OPQR", "E", "a", "aBcD", "T", "und-a-abcd-o-opqr", "@a=abcd;o=opqr"},
{"E", "u", "nu-thai-ca-gregory", "L", "TH", "T", "th-u-ca-gregory-nu-thai", "th@calendar=gregorian;numbers=thai"},
{"L", "en", "K", "tz", "usnyc", "R", "US", "T", "en-US-u-tz-usnyc", "en_US@timezone=america/new_york"},
{"L", "de", "K", "co", "phonebk", "K", "ks", "level1", "K", "kk", "true", "T", "de-u-co-phonebk-kk-true-ks-level1", "de@collation=phonebook;colnormalization=yes;colstrength=primary"},
// {"L", "en", "V", "foooo_barrr", "T", "en-foooo-barrr", "en__FOOOO_BARRR"},
{"st", "L", "en", "R", "us", "T", "en-US", "en_US"},
{"st", "L", "en", "R", "FR", "L", "fr", "T", "fr-FR", "fr_FR"},
{"st", "L", "123", "X"},
{"st", "R", "us", "T", "und-US", "_US"},
{"st", "R", "usa", "X"},
{"st", "R", "123", "L", "en", "T", "en-123", "en_123"},
{"st", "S", "LATN", "L", "DE", "T", "de-Latn", "de_Latn"},
{"st", "S", "latin", "X"},
{"st", "L", "th", "R", "th", "K", "nu", "thai", "T", "th-TH-u-nu-thai", "th_TH@numbers=thai"},
{"st", "E", "z", "ExtZ", "L", "en", "T", "en-z-extz", "en@z=extz"},
{"st", "L", "fr", "R", "FR", "P", "Yoshito-ICU", "T", "fr-FR-x-yoshito-icu", "fr_FR@x=yoshito-icu"},
{"st", "L", "ja", "R", "jp", "K", "ca", "japanese", "T", "ja-JP-u-ca-japanese", "ja_JP@calendar=japanese"},
{"st", "K", "co", "PHONEBK", "K", "ca", "gregory", "L", "De", "T", "de-u-ca-gregory-co-phonebk", "de@calendar=gregorian;collation=phonebook"},
{"st", "E", "o", "OPQR", "E", "a", "aBcD", "T", "und-a-abcd-o-opqr", "@a=abcd;o=opqr"},
{"st", "E", "u", "nu-thai-ca-gregory", "L", "TH", "T", "th-u-ca-gregory-nu-thai", "th@calendar=gregorian;numbers=thai"},
{"st", "L", "en", "K", "tz", "usnyc", "R", "US", "T", "en-US-u-tz-usnyc", "en_US@timezone=america/new_york"},
{"st", "L", "de", "K", "co", "phonebk", "K", "ks", "level1", "K", "kk", "true", "T", "de-u-co-phonebk-kk-true-ks-level1", "de@collation=phonebook;colnormalization=yes;colstrength=primary"},
{"lv", "L", "en", "R", "us", "V", "Windows_XP", "T", "en-US-windows-x-variant-xp", "en_US_WINDOWS_XP"},
};
Builder bld = new Builder();
Builder bld_st = new Builder();
Builder bld_lv = new Builder(true);
for (int tidx = 0; tidx < TESTCASE.length; tidx++) {
bld.clear();
int i = 0;
String[] expected = null;
Builder bld = bld_st;
String bldType = TESTCASE[tidx][i++];
if (bldType.equals("lv")) {
bld = bld_lv;
}
bld.clear();
while (true) {
String method = TESTCASE[tidx][i++];
try {
@ -72,7 +84,7 @@ public class LocaleBuilderTest extends TestFmwk {
} else if (method.equals("K")) {
String key = TESTCASE[tidx][i++];
String type = TESTCASE[tidx][i++];
bld.setLDMLExtensionValue(key, type);
bld.setUnicodeLocaleKeyword(key, type);
} else if (method.equals("E")) {
String key = TESTCASE[tidx][i++];
String value = TESTCASE[tidx][i++];
@ -99,7 +111,7 @@ public class LocaleBuilderTest extends TestFmwk {
}
}
if (expected != null) {
ULocale loc = bld.create();
ULocale loc = bld.build();
if (!expected[1].equals(loc.toString())) {
errln("FAIL: Wrong locale ID - " + loc +
" for test case: " + Arrays.toString(TESTCASE[tidx]));
@ -122,13 +134,13 @@ public class LocaleBuilderTest extends TestFmwk {
Builder bld = new Builder();
try {
bld.setLocale(loc);
ULocale loc1 = bld.create();
ULocale loc1 = bld.build();
if (!loc.equals(loc1)) {
errln("FAIL: Locale loc1 " + loc1 + " was returned by the builder. Expected " + loc);
}
bld.setLanguage("").setLDMLExtensionValue("ca", "buddhist")
.setLanguage("TH").setLDMLExtensionValue("ca", "gregory");
ULocale loc2 = bld.create();
bld.setLanguage("").setUnicodeLocaleKeyword("ca", "buddhist")
.setLanguage("TH").setUnicodeLocaleKeyword("ca", "gregory");
ULocale loc2 = bld.build();
if (!loc.equals(loc2)) {
errln("FAIL: Locale loc2 " + loc2 + " was returned by the builder. Expected " + loc);
}

View File

@ -3752,17 +3752,18 @@ public class ULocaleTest extends TestFmwk {
{"_Latn", "und-Latn"},
{"_DE", "und-DE"},
{"und_FR", "und-FR"},
{"th_TH_TH", "th-TH"},
{"th_TH_TH", "th-TH-x-variant-th"},
{"bogus", "bogus"},
{"foooobarrr", "und"},
//{"az_AZ_CYRL", "az-cyrl-az"}, /* ICU4J does not have this specia locale mapping */
{"aa_BB_CYRL", "aa-BB"},
{"aa_BB_CYRL", "aa-BB-x-variant-cyrl"},
{"en_US_1234", "en-US-1234"},
{"en_US_VARIANTA_VARIANTB", "en-US-varianta-variantb"},
{"en_US_VARIANTB_VARIANTA", "en-US-varianta-variantb"},
{"ja__9876_5432", "ja-5432-9876"},
{"zh_Hant__VAR", "zh-Hant"},
{"es__BADVARIANT_GOODVAR", "es-goodvar"},
{"en_US_VARIANTB_VARIANTA", "en-US-variantb-varianta"},
{"ja__9876_5432", "ja-9876-5432"},
{"zh_Hant__VAR", "zh-Hant-x-variant-var"},
{"es__BADVARIANT_GOODVAR", "es-x-variant-badvariant-goodvar"},
{"es__GOODVAR_BADVARIANT", "es-goodvar-x-variant-badvariant"},
{"en@calendar=gregorian", "en-u-ca-gregory"},
{"de@collation=phonebook;calendar=gregorian", "de-u-ca-gregory-co-phonebk"},
{"th@numbers=thai;z=extz;x=priv-use;a=exta", "th-a-exta-u-nu-thai-z-extz-x-priv-use"},
@ -3782,41 +3783,44 @@ public class ULocaleTest extends TestFmwk {
}
public void TestForLanguageTag() {
final Integer NOERROR = Integer.valueOf(-1);
final Object[][] langtag_to_locale = {
{"zh-cmn-CH", "cmn_CH", new Integer(9)},
{"en", "en", new Integer(2)},
{"en-us", "en_US", new Integer(5)},
{"und-us", "_US", new Integer(6)},
{"und-latn", "_Latn", new Integer(8)},
{"en-us-posix", "en_US_POSIX", new Integer(11)},
{"de-de_euro", "de", new Integer(2)},
{"kok-in", "kok_IN", new Integer(6)},
{"123", "", new Integer(0)},
{"en_us", "", new Integer(0)},
{"en-latn-x", "en_Latn", new Integer(7)},
{"art-lojban", "jbo", new Integer(10)},
{"zh-hakka", "hak", new Integer(8)},
{"zh-cmn-CH", "cmn_CH", new Integer(9)},
{"xxx-yy", "xxx_YY", new Integer(6)},
{"fr-234", "fr_234", new Integer(6)},
{"i-default", "", new Integer(9)},
{"i-test", "", new Integer(0)},
{"ja-jp-jp", "ja_JP", new Integer(5)},
{"bogus", "bogus", new Integer(5)},
{"boguslang", "", new Integer(0)},
{"EN-lATN-us", "en_Latn_US", new Integer(10)},
{"und-variant-1234", "__1234_VARIANT", new Integer(16)},
{"und-varzero-var1-vartwo", "__VARZERO", new Integer(11)},
{"en-u-ca-gregory", "en@calendar=gregorian", new Integer(15)},
{"en-U-cu-USD", "en@currency=usd", new Integer(11)},
{"ar-x-1-2-3", "ar@x=1-2-3", new Integer(10)},
{"fr-u-nu-latn-cu-eur", "fr@currency=eur;numbers=latn", new Integer(19)},
{"de-k-kext-u-co-phonebk-nu-latn", "de@collation=phonebook;k=kext;numbers=latn", new Integer(30)},
{"ja-u-cu-jpy-ca-jp", "ja@currency=jpy", new Integer(/*11*/ 0)}, //TODO: need error index support for invalid LDML extension
{"en-us-u-tz-usnyc", "en_US@timezone=america/new_york", new Integer(16)},
// {"und-a-abc-def", "und@a=abc-def", new Integer(13)}, // ICU4C does not allow an locale with keywords, but without base locale
{"und-a-abc-def", "@a=abc-def", new Integer(13)},
{"zh-u-ca-chinese-x-u-ca-chinese", "zh@calendar=chinese;x=u-ca-chinese", new Integer(30)},
{"ja-u-cu-jpy-ca-jp", "ja@currency=jpy", Integer.valueOf(15)},
{"en", "en", NOERROR},
{"en-us", "en_US", NOERROR},
{"und-us", "_US", NOERROR},
{"und-latn", "_Latn", NOERROR},
{"en-us-posix", "en_US_POSIX", NOERROR},
{"de-de_euro", "de", Integer.valueOf(3)},
{"kok-in", "kok_IN", NOERROR},
{"123", "", Integer.valueOf(0)},
{"en_us", "", Integer.valueOf(0)},
{"en-latn-x", "en_Latn", Integer.valueOf(8)},
{"art-lojban", "jbo", NOERROR},
{"zh-hakka", "hak", NOERROR},
{"zh-cmn-CH", "cmn_CH", NOERROR},
{"xxx-yy", "xxx_YY", NOERROR},
{"fr-234", "fr_234", NOERROR},
{"i-default", "", NOERROR},
{"i-test", "", Integer.valueOf(0)},
{"ja-jp-jp", "ja_JP", Integer.valueOf(6)},
{"bogus", "bogus", NOERROR},
{"boguslang", "", Integer.valueOf(0)},
{"EN-lATN-us", "en_Latn_US", NOERROR},
{"und-variant-1234", "__VARIANT_1234", NOERROR},
{"und-varzero-var1-vartwo", "__VARZERO", Integer.valueOf(12)},
{"en-u-ca-gregory", "en@calendar=gregorian", NOERROR},
{"en-U-cu-USD", "en@currency=usd", NOERROR},
{"ar-x-1-2-3", "ar@x=1-2-3", NOERROR},
{"fr-u-nu-latn-cu-eur", "fr@currency=eur;numbers=latn", NOERROR},
{"de-k-kext-u-co-phonebk-nu-latn", "de@collation=phonebook;k=kext;numbers=latn", NOERROR},
{"ja-u-cu-jpy-ca-jp", "ja@currency=jpy", Integer.valueOf(15)},
{"en-us-u-tz-usnyc", "en_US@timezone=america/new_york", NOERROR},
{"und-a-abc-def", "@a=abc-def", NOERROR},
{"zh-u-ca-chinese-x-u-ca-chinese", "zh@calendar=chinese;x=u-ca-chinese", NOERROR},
{"fr--FR", "fr", Integer.valueOf(3)},
{"fr-", "fr", Integer.valueOf(3)},
};
for (int i = 0; i < langtag_to_locale.length; i++) {
@ -3830,36 +3834,33 @@ public class ULocaleTest extends TestFmwk {
}
}
// Use locale builder to check the parsed length
// Use locale builder to check errors
for (int i = 0; i < langtag_to_locale.length; i++) {
String tag = (String)langtag_to_locale[i][0];
ULocale expected = new ULocale((String)langtag_to_locale[i][1]);
int inLen = tag.length();
int validLen = ((Integer)langtag_to_locale[i][2]).intValue();
int errorIdx = ((Integer)langtag_to_locale[i][2]).intValue();
try {
Builder bld = new Builder();
bld.setLanguageTag(tag);
ULocale loc = bld.create();
ULocale loc = bld.build();
if (!loc.equals(expected)) {
errln("FAIL: forLanguageTag returned locale [" + loc + "] for language tag [" + tag
+ "] - expected: [" + expected + "]");
}
if (inLen != validLen) {
if (errorIdx != NOERROR.intValue()) {
errln("FAIL: Builder.setLanguageTag should throw an exception for input tag [" + tag + "]");
}
} catch (IllformedLocaleException ifle) {
int errorIdx = ifle.getErrorIndex();
int expectedErrIdx = (validLen == 0) ? 0 : validLen + 1;
if (errorIdx != expectedErrIdx) {
errln("FAIL: Builder.setLanguageTag returned error index " + errorIdx
+ " for input language tag [" + tag + "] expected: " + expectedErrIdx);
if (ifle.getErrorIndex() != errorIdx) {
errln("FAIL: Builder.setLanguageTag returned error index " + ifle.getErrorIndex()
+ " for input language tag [" + tag + "] expected: " + errorIdx);
}
}
}
}
/*
* Test that if you use any locale without keyword that you will get a NULL
* string returned and not throw and exception.