2712N/A/*
3966N/A * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
2712N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
2712N/A *
2712N/A * This code is free software; you can redistribute it and/or modify it
2712N/A * under the terms of the GNU General Public License version 2 only, as
2712N/A * published by the Free Software Foundation. Oracle designates this
2712N/A * particular file as subject to the "Classpath" exception as provided
2712N/A * by Oracle in the LICENSE file that accompanied this code.
2712N/A *
2712N/A * This code is distributed in the hope that it will be useful, but WITHOUT
2712N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
2712N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
2712N/A * version 2 for more details (a copy is included in the LICENSE file that
2712N/A * accompanied this code).
2712N/A *
2712N/A * You should have received a copy of the GNU General Public License version
2712N/A * 2 along with this work; if not, write to the Free Software Foundation,
2712N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2712N/A *
2712N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2712N/A * or visit www.oracle.com if you need additional information or have any
2712N/A * questions.
2712N/A */
2712N/A
2712N/A/*
2712N/A *******************************************************************************
2712N/A * Copyright (C) 2009-2010, International Business Machines Corporation and *
2712N/A * others. All Rights Reserved. *
2712N/A *******************************************************************************
2712N/A */
2712N/Apackage sun.util.locale;
2712N/A
2712N/Aimport java.util.ArrayList;
2712N/Aimport java.util.HashMap;
2712N/Aimport java.util.HashSet;
2712N/Aimport java.util.List;
3966N/Aimport java.util.Map;
2712N/Aimport java.util.Set;
2712N/A
2712N/Apublic final class InternalLocaleBuilder {
2712N/A
3966N/A private static final CaseInsensitiveChar PRIVATEUSE_KEY
3966N/A = new CaseInsensitiveChar(LanguageTag.PRIVATEUSE);
2712N/A
3966N/A private String language = "";
3966N/A private String script = "";
3966N/A private String region = "";
3966N/A private String variant = "";
2712N/A
3966N/A private Map<CaseInsensitiveChar, String> extensions;
3966N/A private Set<CaseInsensitiveString> uattributes;
3966N/A private Map<CaseInsensitiveString, String> ukeywords;
2712N/A
2712N/A
2712N/A public InternalLocaleBuilder() {
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder setLanguage(String language) throws LocaleSyntaxException {
3966N/A if (LocaleUtils.isEmpty(language)) {
3966N/A this.language = "";
2712N/A } else {
2712N/A if (!LanguageTag.isLanguage(language)) {
2712N/A throw new LocaleSyntaxException("Ill-formed language: " + language, 0);
2712N/A }
3966N/A this.language = language;
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder setScript(String script) throws LocaleSyntaxException {
3966N/A if (LocaleUtils.isEmpty(script)) {
3966N/A this.script = "";
2712N/A } else {
2712N/A if (!LanguageTag.isScript(script)) {
2712N/A throw new LocaleSyntaxException("Ill-formed script: " + script, 0);
2712N/A }
3966N/A this.script = script;
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder setRegion(String region) throws LocaleSyntaxException {
3966N/A if (LocaleUtils.isEmpty(region)) {
3966N/A this.region = "";
2712N/A } else {
2712N/A if (!LanguageTag.isRegion(region)) {
2712N/A throw new LocaleSyntaxException("Ill-formed region: " + region, 0);
2712N/A }
3966N/A this.region = region;
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder setVariant(String variant) throws LocaleSyntaxException {
3966N/A if (LocaleUtils.isEmpty(variant)) {
3966N/A this.variant = "";
2712N/A } else {
2712N/A // normalize separators to "_"
2712N/A String var = variant.replaceAll(LanguageTag.SEP, BaseLocale.SEP);
2712N/A int errIdx = checkVariants(var, BaseLocale.SEP);
2712N/A if (errIdx != -1) {
2712N/A throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
2712N/A }
3966N/A this.variant = var;
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder addUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
2712N/A if (!UnicodeLocaleExtension.isAttribute(attribute)) {
2712N/A throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
2712N/A }
2712N/A // Use case insensitive string to prevent duplication
3966N/A if (uattributes == null) {
3966N/A uattributes = new HashSet<>(4);
2712N/A }
3966N/A uattributes.add(new CaseInsensitiveString(attribute));
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder removeUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
2712N/A if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
2712N/A throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
2712N/A }
3966N/A if (uattributes != null) {
3966N/A uattributes.remove(new CaseInsensitiveString(attribute));
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder setUnicodeLocaleKeyword(String key, String type) throws LocaleSyntaxException {
2712N/A if (!UnicodeLocaleExtension.isKey(key)) {
2712N/A throw new LocaleSyntaxException("Ill-formed Unicode locale keyword key: " + key);
2712N/A }
2712N/A
2712N/A CaseInsensitiveString cikey = new CaseInsensitiveString(key);
2712N/A if (type == null) {
3966N/A if (ukeywords != null) {
2712N/A // null type is used for remove the key
3966N/A ukeywords.remove(cikey);
2712N/A }
2712N/A } else {
2712N/A if (type.length() != 0) {
2712N/A // normalize separator to "-"
2712N/A String tp = type.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
2712N/A // validate
2712N/A StringTokenIterator itr = new StringTokenIterator(tp, LanguageTag.SEP);
2712N/A while (!itr.isDone()) {
2712N/A String s = itr.current();
2712N/A if (!UnicodeLocaleExtension.isTypeSubtag(s)) {
3966N/A throw new LocaleSyntaxException("Ill-formed Unicode locale keyword type: "
3966N/A + type,
3966N/A itr.currentStart());
2712N/A }
2712N/A itr.next();
2712N/A }
2712N/A }
3966N/A if (ukeywords == null) {
3966N/A ukeywords = new HashMap<>(4);
2712N/A }
3966N/A ukeywords.put(cikey, type);
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder setExtension(char singleton, String value) throws LocaleSyntaxException {
2712N/A // validate key
2712N/A boolean isBcpPrivateuse = LanguageTag.isPrivateusePrefixChar(singleton);
2712N/A if (!isBcpPrivateuse && !LanguageTag.isExtensionSingletonChar(singleton)) {
2712N/A throw new LocaleSyntaxException("Ill-formed extension key: " + singleton);
2712N/A }
2712N/A
3966N/A boolean remove = LocaleUtils.isEmpty(value);
2712N/A CaseInsensitiveChar key = new CaseInsensitiveChar(singleton);
2712N/A
2712N/A if (remove) {
2712N/A if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
2712N/A // clear entire Unicode locale extension
3966N/A if (uattributes != null) {
3966N/A uattributes.clear();
2712N/A }
3966N/A if (ukeywords != null) {
3966N/A ukeywords.clear();
2712N/A }
2712N/A } else {
3966N/A if (extensions != null && extensions.containsKey(key)) {
3966N/A extensions.remove(key);
2712N/A }
2712N/A }
2712N/A } else {
2712N/A // validate value
2712N/A String val = value.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
2712N/A StringTokenIterator itr = new StringTokenIterator(val, LanguageTag.SEP);
2712N/A while (!itr.isDone()) {
2712N/A String s = itr.current();
2712N/A boolean validSubtag;
2712N/A if (isBcpPrivateuse) {
2712N/A validSubtag = LanguageTag.isPrivateuseSubtag(s);
2712N/A } else {
2712N/A validSubtag = LanguageTag.isExtensionSubtag(s);
2712N/A }
2712N/A if (!validSubtag) {
3966N/A throw new LocaleSyntaxException("Ill-formed extension value: " + s,
3966N/A itr.currentStart());
2712N/A }
2712N/A itr.next();
2712N/A }
2712N/A
2712N/A if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
2712N/A setUnicodeLocaleExtension(val);
2712N/A } else {
3966N/A if (extensions == null) {
3966N/A extensions = new HashMap<>(4);
2712N/A }
3966N/A extensions.put(key, val);
2712N/A }
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A /*
2712N/A * Set extension/private subtags in a single string representation
2712N/A */
2712N/A public InternalLocaleBuilder setExtensions(String subtags) throws LocaleSyntaxException {
3966N/A if (LocaleUtils.isEmpty(subtags)) {
2712N/A clearExtensions();
2712N/A return this;
2712N/A }
2712N/A subtags = subtags.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
2712N/A StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
2712N/A
2712N/A List<String> extensions = null;
2712N/A String privateuse = null;
2712N/A
2712N/A int parsed = 0;
2712N/A int start;
2712N/A
2712N/A // Make a list of extension subtags
2712N/A while (!itr.isDone()) {
2712N/A String s = itr.current();
2712N/A if (LanguageTag.isExtensionSingleton(s)) {
2712N/A start = itr.currentStart();
2712N/A String singleton = s;
2712N/A StringBuilder sb = new StringBuilder(singleton);
2712N/A
2712N/A itr.next();
2712N/A while (!itr.isDone()) {
2712N/A s = itr.current();
2712N/A if (LanguageTag.isExtensionSubtag(s)) {
2712N/A sb.append(LanguageTag.SEP).append(s);
2712N/A parsed = itr.currentEnd();
2712N/A } else {
2712N/A break;
2712N/A }
2712N/A itr.next();
2712N/A }
2712N/A
2712N/A if (parsed < start) {
3966N/A throw new LocaleSyntaxException("Incomplete extension '" + singleton + "'",
3966N/A start);
2712N/A }
2712N/A
2712N/A if (extensions == null) {
3966N/A extensions = new ArrayList<>(4);
2712N/A }
2712N/A extensions.add(sb.toString());
2712N/A } else {
2712N/A break;
2712N/A }
2712N/A }
2712N/A if (!itr.isDone()) {
2712N/A String s = itr.current();
2712N/A if (LanguageTag.isPrivateusePrefix(s)) {
2712N/A start = itr.currentStart();
2712N/A StringBuilder sb = new StringBuilder(s);
2712N/A
2712N/A itr.next();
2712N/A while (!itr.isDone()) {
2712N/A s = itr.current();
2712N/A if (!LanguageTag.isPrivateuseSubtag(s)) {
2712N/A break;
2712N/A }
2712N/A sb.append(LanguageTag.SEP).append(s);
2712N/A parsed = itr.currentEnd();
2712N/A
2712N/A itr.next();
2712N/A }
2712N/A if (parsed <= start) {
3966N/A throw new LocaleSyntaxException("Incomplete privateuse:"
3966N/A + subtags.substring(start),
3966N/A start);
2712N/A } else {
2712N/A privateuse = sb.toString();
2712N/A }
2712N/A }
2712N/A }
2712N/A
2712N/A if (!itr.isDone()) {
3966N/A throw new LocaleSyntaxException("Ill-formed extension subtags:"
3966N/A + subtags.substring(itr.currentStart()),
3966N/A itr.currentStart());
2712N/A }
2712N/A
2712N/A return setExtensions(extensions, privateuse);
2712N/A }
2712N/A
2712N/A /*
2712N/A * Set a list of BCP47 extensions and private use subtags
2712N/A * BCP47 extensions are already validated and well-formed, but may contain duplicates
2712N/A */
2712N/A private InternalLocaleBuilder setExtensions(List<String> bcpExtensions, String privateuse) {
2712N/A clearExtensions();
2712N/A
3966N/A if (!LocaleUtils.isEmpty(bcpExtensions)) {
3966N/A Set<CaseInsensitiveChar> done = new HashSet<>(bcpExtensions.size());
2712N/A for (String bcpExt : bcpExtensions) {
3966N/A CaseInsensitiveChar key = new CaseInsensitiveChar(bcpExt);
2712N/A // ignore duplicates
3966N/A if (!done.contains(key)) {
2712N/A // each extension string contains singleton, e.g. "a-abc-def"
2712N/A if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
2712N/A setUnicodeLocaleExtension(bcpExt.substring(2));
2712N/A } else {
3966N/A if (extensions == null) {
3966N/A extensions = new HashMap<>(4);
2712N/A }
3966N/A extensions.put(key, bcpExt.substring(2));
2712N/A }
2712N/A }
3966N/A done.add(key);
2712N/A }
2712N/A }
2712N/A if (privateuse != null && privateuse.length() > 0) {
2712N/A // privateuse string contains prefix, e.g. "x-abc-def"
3966N/A if (extensions == null) {
3966N/A extensions = new HashMap<>(1);
2712N/A }
3966N/A extensions.put(new CaseInsensitiveChar(privateuse), privateuse.substring(2));
2712N/A }
2712N/A
2712N/A return this;
2712N/A }
2712N/A
2712N/A /*
2712N/A * Reset Builder's internal state with the given language tag
2712N/A */
2712N/A public InternalLocaleBuilder setLanguageTag(LanguageTag langtag) {
2712N/A clear();
3966N/A if (!langtag.getExtlangs().isEmpty()) {
3966N/A language = langtag.getExtlangs().get(0);
2712N/A } else {
3966N/A String lang = langtag.getLanguage();
3966N/A if (!lang.equals(LanguageTag.UNDETERMINED)) {
3966N/A language = lang;
2712N/A }
2712N/A }
3966N/A script = langtag.getScript();
3966N/A region = langtag.getRegion();
2712N/A
2712N/A List<String> bcpVariants = langtag.getVariants();
3966N/A if (!bcpVariants.isEmpty()) {
2712N/A StringBuilder var = new StringBuilder(bcpVariants.get(0));
3966N/A int size = bcpVariants.size();
3966N/A for (int i = 1; i < size; i++) {
2712N/A var.append(BaseLocale.SEP).append(bcpVariants.get(i));
2712N/A }
3966N/A variant = var.toString();
2712N/A }
2712N/A
2712N/A setExtensions(langtag.getExtensions(), langtag.getPrivateuse());
2712N/A
2712N/A return this;
2712N/A }
2712N/A
3966N/A public InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions localeExtensions) throws LocaleSyntaxException {
2712N/A String language = base.getLanguage();
2712N/A String script = base.getScript();
2712N/A String region = base.getRegion();
2712N/A String variant = base.getVariant();
2712N/A
2712N/A // Special backward compatibility support
2712N/A
2712N/A // Exception 1 - ja_JP_JP
2712N/A if (language.equals("ja") && region.equals("JP") && variant.equals("JP")) {
2712N/A // When locale ja_JP_JP is created, ca-japanese is always there.
2712N/A // The builder ignores the variant "JP"
3966N/A assert("japanese".equals(localeExtensions.getUnicodeLocaleType("ca")));
2712N/A variant = "";
2712N/A }
2712N/A // Exception 2 - th_TH_TH
2712N/A else if (language.equals("th") && region.equals("TH") && variant.equals("TH")) {
2712N/A // When locale th_TH_TH is created, nu-thai is always there.
2712N/A // The builder ignores the variant "TH"
3966N/A assert("thai".equals(localeExtensions.getUnicodeLocaleType("nu")));
2712N/A variant = "";
2712N/A }
2712N/A // Exception 3 - no_NO_NY
2712N/A else if (language.equals("no") && region.equals("NO") && variant.equals("NY")) {
2712N/A // no_NO_NY is a valid locale and used by Java 6 or older versions.
2712N/A // The build ignores the variant "NY" and change the language to "nn".
2712N/A language = "nn";
2712N/A variant = "";
2712N/A }
2712N/A
2712N/A // Validate base locale fields before updating internal state.
2712N/A // LocaleExtensions always store validated/canonicalized values,
2712N/A // so no checks are necessary.
2712N/A if (language.length() > 0 && !LanguageTag.isLanguage(language)) {
2712N/A throw new LocaleSyntaxException("Ill-formed language: " + language);
2712N/A }
2712N/A
2712N/A if (script.length() > 0 && !LanguageTag.isScript(script)) {
2712N/A throw new LocaleSyntaxException("Ill-formed script: " + script);
2712N/A }
2712N/A
2712N/A if (region.length() > 0 && !LanguageTag.isRegion(region)) {
2712N/A throw new LocaleSyntaxException("Ill-formed region: " + region);
2712N/A }
2712N/A
2712N/A if (variant.length() > 0) {
2712N/A int errIdx = checkVariants(variant, BaseLocale.SEP);
2712N/A if (errIdx != -1) {
2712N/A throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
2712N/A }
2712N/A }
2712N/A
2712N/A // The input locale is validated at this point.
2712N/A // Now, updating builder's internal fields.
3966N/A this.language = language;
3966N/A this.script = script;
3966N/A this.region = region;
3966N/A this.variant = variant;
2712N/A clearExtensions();
2712N/A
3966N/A Set<Character> extKeys = (localeExtensions == null) ? null : localeExtensions.getKeys();
2712N/A if (extKeys != null) {
3966N/A // map localeExtensions back to builder's internal format
2712N/A for (Character key : extKeys) {
3966N/A Extension e = localeExtensions.getExtension(key);
2712N/A if (e instanceof UnicodeLocaleExtension) {
2712N/A UnicodeLocaleExtension ue = (UnicodeLocaleExtension)e;
2712N/A for (String uatr : ue.getUnicodeLocaleAttributes()) {
3966N/A if (uattributes == null) {
3966N/A uattributes = new HashSet<>(4);
2712N/A }
3966N/A uattributes.add(new CaseInsensitiveString(uatr));
2712N/A }
2712N/A for (String ukey : ue.getUnicodeLocaleKeys()) {
3966N/A if (ukeywords == null) {
3966N/A ukeywords = new HashMap<>(4);
2712N/A }
3966N/A ukeywords.put(new CaseInsensitiveString(ukey), ue.getUnicodeLocaleType(ukey));
2712N/A }
2712N/A } else {
3966N/A if (extensions == null) {
3966N/A extensions = new HashMap<>(4);
2712N/A }
3966N/A extensions.put(new CaseInsensitiveChar(key), e.getValue());
2712N/A }
2712N/A }
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder clear() {
3966N/A language = "";
3966N/A script = "";
3966N/A region = "";
3966N/A variant = "";
2712N/A clearExtensions();
2712N/A return this;
2712N/A }
2712N/A
2712N/A public InternalLocaleBuilder clearExtensions() {
3966N/A if (extensions != null) {
3966N/A extensions.clear();
2712N/A }
3966N/A if (uattributes != null) {
3966N/A uattributes.clear();
2712N/A }
3966N/A if (ukeywords != null) {
3966N/A ukeywords.clear();
2712N/A }
2712N/A return this;
2712N/A }
2712N/A
2712N/A public BaseLocale getBaseLocale() {
3966N/A String language = this.language;
3966N/A String script = this.script;
3966N/A String region = this.region;
3966N/A String variant = this.variant;
2712N/A
2712N/A // Special private use subtag sequence identified by "lvariant" will be
2712N/A // interpreted as Java variant.
3966N/A if (extensions != null) {
3966N/A String privuse = extensions.get(PRIVATEUSE_KEY);
2712N/A if (privuse != null) {
2712N/A StringTokenIterator itr = new StringTokenIterator(privuse, LanguageTag.SEP);
2712N/A boolean sawPrefix = false;
2712N/A int privVarStart = -1;
2712N/A while (!itr.isDone()) {
2712N/A if (sawPrefix) {
2712N/A privVarStart = itr.currentStart();
2712N/A break;
2712N/A }
3966N/A if (LocaleUtils.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
2712N/A sawPrefix = true;
2712N/A }
2712N/A itr.next();
2712N/A }
2712N/A if (privVarStart != -1) {
2712N/A StringBuilder sb = new StringBuilder(variant);
2712N/A if (sb.length() != 0) {
2712N/A sb.append(BaseLocale.SEP);
2712N/A }
3966N/A sb.append(privuse.substring(privVarStart).replaceAll(LanguageTag.SEP,
3966N/A BaseLocale.SEP));
2712N/A variant = sb.toString();
2712N/A }
2712N/A }
2712N/A }
2712N/A
2712N/A return BaseLocale.getInstance(language, script, region, variant);
2712N/A }
2712N/A
2712N/A public LocaleExtensions getLocaleExtensions() {
3966N/A if (LocaleUtils.isEmpty(extensions) && LocaleUtils.isEmpty(uattributes)
3966N/A && LocaleUtils.isEmpty(ukeywords)) {
3966N/A return null;
2712N/A }
2712N/A
3966N/A LocaleExtensions lext = new LocaleExtensions(extensions, uattributes, ukeywords);
3966N/A return lext.isEmpty() ? null : lext;
2712N/A }
2712N/A
2712N/A /*
2712N/A * Remove special private use subtag sequence identified by "lvariant"
2712N/A * and return the rest. Only used by LocaleExtensions
2712N/A */
2712N/A static String removePrivateuseVariant(String privuseVal) {
2712N/A StringTokenIterator itr = new StringTokenIterator(privuseVal, LanguageTag.SEP);
2712N/A
2712N/A // Note: privateuse value "abc-lvariant" is unchanged
2712N/A // because no subtags after "lvariant".
2712N/A
2712N/A int prefixStart = -1;
2712N/A boolean sawPrivuseVar = false;
2712N/A while (!itr.isDone()) {
2712N/A if (prefixStart != -1) {
2712N/A // Note: privateuse value "abc-lvariant" is unchanged
2712N/A // because no subtags after "lvariant".
2712N/A sawPrivuseVar = true;
2712N/A break;
2712N/A }
3966N/A if (LocaleUtils.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
2712N/A prefixStart = itr.currentStart();
2712N/A }
2712N/A itr.next();
2712N/A }
2712N/A if (!sawPrivuseVar) {
2712N/A return privuseVal;
2712N/A }
2712N/A
2712N/A assert(prefixStart == 0 || prefixStart > 1);
2712N/A return (prefixStart == 0) ? null : privuseVal.substring(0, prefixStart -1);
2712N/A }
2712N/A
2712N/A /*
2712N/A * Check if the given variant subtags separated by the given
2712N/A * separator(s) are valid
2712N/A */
2712N/A private int checkVariants(String variants, String sep) {
2712N/A StringTokenIterator itr = new StringTokenIterator(variants, sep);
2712N/A while (!itr.isDone()) {
2712N/A String s = itr.current();
2712N/A if (!LanguageTag.isVariant(s)) {
2712N/A return itr.currentStart();
2712N/A }
2712N/A itr.next();
2712N/A }
2712N/A return -1;
2712N/A }
2712N/A
2712N/A /*
2712N/A * Private methods parsing Unicode Locale Extension subtags.
2712N/A * Duplicated attributes/keywords will be ignored.
2712N/A * The input must be a valid extension subtags (excluding singleton).
2712N/A */
2712N/A private void setUnicodeLocaleExtension(String subtags) {
2712N/A // wipe out existing attributes/keywords
3966N/A if (uattributes != null) {
3966N/A uattributes.clear();
2712N/A }
3966N/A if (ukeywords != null) {
3966N/A ukeywords.clear();
2712N/A }
2712N/A
2712N/A StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
2712N/A
2712N/A // parse attributes
2712N/A while (!itr.isDone()) {
2712N/A if (!UnicodeLocaleExtension.isAttribute(itr.current())) {
2712N/A break;
2712N/A }
3966N/A if (uattributes == null) {
3966N/A uattributes = new HashSet<>(4);
2712N/A }
3966N/A uattributes.add(new CaseInsensitiveString(itr.current()));
2712N/A itr.next();
2712N/A }
2712N/A
2712N/A // parse keywords
2712N/A CaseInsensitiveString key = null;
2712N/A String type;
2712N/A int typeStart = -1;
2712N/A int typeEnd = -1;
2712N/A while (!itr.isDone()) {
2712N/A if (key != null) {
2712N/A if (UnicodeLocaleExtension.isKey(itr.current())) {
2712N/A // next keyword - emit previous one
2712N/A assert(typeStart == -1 || typeEnd != -1);
2712N/A type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
3966N/A if (ukeywords == null) {
3966N/A ukeywords = new HashMap<>(4);
2712N/A }
3966N/A ukeywords.put(key, type);
2712N/A
2712N/A // reset keyword info
2712N/A CaseInsensitiveString tmpKey = new CaseInsensitiveString(itr.current());
3966N/A key = ukeywords.containsKey(tmpKey) ? null : tmpKey;
2712N/A typeStart = typeEnd = -1;
2712N/A } else {
2712N/A if (typeStart == -1) {
2712N/A typeStart = itr.currentStart();
2712N/A }
2712N/A typeEnd = itr.currentEnd();
2712N/A }
2712N/A } else if (UnicodeLocaleExtension.isKey(itr.current())) {
2712N/A // 1. first keyword or
2712N/A // 2. next keyword, but previous one was duplicate
2712N/A key = new CaseInsensitiveString(itr.current());
3966N/A if (ukeywords != null && ukeywords.containsKey(key)) {
2712N/A // duplicate
2712N/A key = null;
2712N/A }
2712N/A }
2712N/A
2712N/A if (!itr.hasNext()) {
2712N/A if (key != null) {
2712N/A // last keyword
2712N/A assert(typeStart == -1 || typeEnd != -1);
2712N/A type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
3966N/A if (ukeywords == null) {
3966N/A ukeywords = new HashMap<>(4);
2712N/A }
3966N/A ukeywords.put(key, type);
2712N/A }
2712N/A break;
2712N/A }
2712N/A
2712N/A itr.next();
2712N/A }
2712N/A }
2712N/A
3966N/A static final class CaseInsensitiveString {
3966N/A private final String str, lowerStr;
2712N/A
2712N/A CaseInsensitiveString(String s) {
3966N/A str = s;
3966N/A lowerStr = LocaleUtils.toLowerString(s);
2712N/A }
2712N/A
2712N/A public String value() {
3966N/A return str;
2712N/A }
2712N/A
3966N/A @Override
2712N/A public int hashCode() {
3966N/A return lowerStr.hashCode();
2712N/A }
2712N/A
3966N/A @Override
2712N/A public boolean equals(Object obj) {
2712N/A if (this == obj) {
2712N/A return true;
2712N/A }
2712N/A if (!(obj instanceof CaseInsensitiveString)) {
2712N/A return false;
2712N/A }
3966N/A return lowerStr.equals(((CaseInsensitiveString)obj).lowerStr);
2712N/A }
2712N/A }
2712N/A
3966N/A static final class CaseInsensitiveChar {
3966N/A private final char ch, lowerCh;
3966N/A
3966N/A /**
3966N/A * Constructs a CaseInsensitiveChar with the first char of the
3966N/A * given s.
3966N/A */
3966N/A private CaseInsensitiveChar(String s) {
3966N/A this(s.charAt(0));
3966N/A }
2712N/A
2712N/A CaseInsensitiveChar(char c) {
3966N/A ch = c;
3966N/A lowerCh = LocaleUtils.toLower(ch);
2712N/A }
2712N/A
2712N/A public char value() {
3966N/A return ch;
2712N/A }
2712N/A
3966N/A @Override
2712N/A public int hashCode() {
3966N/A return lowerCh;
2712N/A }
2712N/A
3966N/A @Override
2712N/A public boolean equals(Object obj) {
2712N/A if (this == obj) {
2712N/A return true;
2712N/A }
2712N/A if (!(obj instanceof CaseInsensitiveChar)) {
2712N/A return false;
2712N/A }
3966N/A return lowerCh == ((CaseInsensitiveChar)obj).lowerCh;
2712N/A }
2712N/A }
2712N/A}