jdk/src/share/classes/sun/util/locale/InternalLocaleBuilder.java
changeset 9224 75c0420badef
parent 6501 684810d882b3
equal deleted inserted replaced
9223:d331b7996fc3 9224:75c0420badef
     1 /*
     1 /*
     2  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
     2  * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Oracle designates this
     7  * published by the Free Software Foundation.  Oracle designates this
    33 
    33 
    34 import java.util.ArrayList;
    34 import java.util.ArrayList;
    35 import java.util.HashMap;
    35 import java.util.HashMap;
    36 import java.util.HashSet;
    36 import java.util.HashSet;
    37 import java.util.List;
    37 import java.util.List;
       
    38 import java.util.Map;
    38 import java.util.Set;
    39 import java.util.Set;
    39 
    40 
    40 public final class InternalLocaleBuilder {
    41 public final class InternalLocaleBuilder {
    41 
    42 
    42     private String _language = "";
    43     private static final CaseInsensitiveChar PRIVATEUSE_KEY
    43     private String _script = "";
    44         = new CaseInsensitiveChar(LanguageTag.PRIVATEUSE);
    44     private String _region = "";
    45 
    45     private String _variant = "";
    46     private String language = "";
    46 
    47     private String script = "";
    47     private static final CaseInsensitiveChar PRIVUSE_KEY = new CaseInsensitiveChar(LanguageTag.PRIVATEUSE.charAt(0));
    48     private String region = "";
    48 
    49     private String variant = "";
    49     private HashMap<CaseInsensitiveChar, String> _extensions;
    50 
    50     private HashSet<CaseInsensitiveString> _uattributes;
    51     private Map<CaseInsensitiveChar, String> extensions;
    51     private HashMap<CaseInsensitiveString, String> _ukeywords;
    52     private Set<CaseInsensitiveString> uattributes;
       
    53     private Map<CaseInsensitiveString, String> ukeywords;
    52 
    54 
    53 
    55 
    54     public InternalLocaleBuilder() {
    56     public InternalLocaleBuilder() {
    55     }
    57     }
    56 
    58 
    57     public InternalLocaleBuilder setLanguage(String language) throws LocaleSyntaxException {
    59     public InternalLocaleBuilder setLanguage(String language) throws LocaleSyntaxException {
    58         if (language == null || language.length() == 0) {
    60         if (LocaleUtils.isEmpty(language)) {
    59             _language = "";
    61             this.language = "";
    60         } else {
    62         } else {
    61             if (!LanguageTag.isLanguage(language)) {
    63             if (!LanguageTag.isLanguage(language)) {
    62                 throw new LocaleSyntaxException("Ill-formed language: " + language, 0);
    64                 throw new LocaleSyntaxException("Ill-formed language: " + language, 0);
    63             }
    65             }
    64             _language = language;
    66             this.language = language;
    65         }
    67         }
    66         return this;
    68         return this;
    67     }
    69     }
    68 
    70 
    69     public InternalLocaleBuilder setScript(String script) throws LocaleSyntaxException {
    71     public InternalLocaleBuilder setScript(String script) throws LocaleSyntaxException {
    70         if (script == null || script.length() == 0) {
    72         if (LocaleUtils.isEmpty(script)) {
    71             _script = "";
    73             this.script = "";
    72         } else {
    74         } else {
    73             if (!LanguageTag.isScript(script)) {
    75             if (!LanguageTag.isScript(script)) {
    74                 throw new LocaleSyntaxException("Ill-formed script: " + script, 0);
    76                 throw new LocaleSyntaxException("Ill-formed script: " + script, 0);
    75             }
    77             }
    76             _script = script;
    78             this.script = script;
    77         }
    79         }
    78         return this;
    80         return this;
    79     }
    81     }
    80 
    82 
    81     public InternalLocaleBuilder setRegion(String region) throws LocaleSyntaxException {
    83     public InternalLocaleBuilder setRegion(String region) throws LocaleSyntaxException {
    82         if (region == null || region.length() == 0) {
    84         if (LocaleUtils.isEmpty(region)) {
    83             _region = "";
    85             this.region = "";
    84         } else {
    86         } else {
    85             if (!LanguageTag.isRegion(region)) {
    87             if (!LanguageTag.isRegion(region)) {
    86                 throw new LocaleSyntaxException("Ill-formed region: " + region, 0);
    88                 throw new LocaleSyntaxException("Ill-formed region: " + region, 0);
    87             }
    89             }
    88             _region = region;
    90             this.region = region;
    89         }
    91         }
    90         return this;
    92         return this;
    91     }
    93     }
    92 
    94 
    93     public InternalLocaleBuilder setVariant(String variant) throws LocaleSyntaxException {
    95     public InternalLocaleBuilder setVariant(String variant) throws LocaleSyntaxException {
    94         if (variant == null || variant.length() == 0) {
    96         if (LocaleUtils.isEmpty(variant)) {
    95             _variant = "";
    97             this.variant = "";
    96         } else {
    98         } else {
    97             // normalize separators to "_"
    99             // normalize separators to "_"
    98             String var = variant.replaceAll(LanguageTag.SEP, BaseLocale.SEP);
   100             String var = variant.replaceAll(LanguageTag.SEP, BaseLocale.SEP);
    99             int errIdx = checkVariants(var, BaseLocale.SEP);
   101             int errIdx = checkVariants(var, BaseLocale.SEP);
   100             if (errIdx != -1) {
   102             if (errIdx != -1) {
   101                 throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
   103                 throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
   102             }
   104             }
   103             _variant = var;
   105             this.variant = var;
   104         }
   106         }
   105         return this;
   107         return this;
   106     }
   108     }
   107 
   109 
   108     public InternalLocaleBuilder addUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
   110     public InternalLocaleBuilder addUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
   109         if (!UnicodeLocaleExtension.isAttribute(attribute)) {
   111         if (!UnicodeLocaleExtension.isAttribute(attribute)) {
   110             throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
   112             throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
   111         }
   113         }
   112         // Use case insensitive string to prevent duplication
   114         // Use case insensitive string to prevent duplication
   113         if (_uattributes == null) {
   115         if (uattributes == null) {
   114             _uattributes = new HashSet<CaseInsensitiveString>(4);
   116             uattributes = new HashSet<>(4);
   115         }
   117         }
   116         _uattributes.add(new CaseInsensitiveString(attribute));
   118         uattributes.add(new CaseInsensitiveString(attribute));
   117         return this;
   119         return this;
   118     }
   120     }
   119 
   121 
   120     public InternalLocaleBuilder removeUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
   122     public InternalLocaleBuilder removeUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
   121         if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
   123         if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
   122             throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
   124             throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
   123         }
   125         }
   124         if (_uattributes != null) {
   126         if (uattributes != null) {
   125             _uattributes.remove(new CaseInsensitiveString(attribute));
   127             uattributes.remove(new CaseInsensitiveString(attribute));
   126         }
   128         }
   127         return this;
   129         return this;
   128     }
   130     }
   129 
   131 
   130     public InternalLocaleBuilder setUnicodeLocaleKeyword(String key, String type) throws LocaleSyntaxException {
   132     public InternalLocaleBuilder setUnicodeLocaleKeyword(String key, String type) throws LocaleSyntaxException {
   132             throw new LocaleSyntaxException("Ill-formed Unicode locale keyword key: " + key);
   134             throw new LocaleSyntaxException("Ill-formed Unicode locale keyword key: " + key);
   133         }
   135         }
   134 
   136 
   135         CaseInsensitiveString cikey = new CaseInsensitiveString(key);
   137         CaseInsensitiveString cikey = new CaseInsensitiveString(key);
   136         if (type == null) {
   138         if (type == null) {
   137             if (_ukeywords != null) {
   139             if (ukeywords != null) {
   138                 // null type is used for remove the key
   140                 // null type is used for remove the key
   139                 _ukeywords.remove(cikey);
   141                 ukeywords.remove(cikey);
   140             }
   142             }
   141         } else {
   143         } else {
   142             if (type.length() != 0) {
   144             if (type.length() != 0) {
   143                 // normalize separator to "-"
   145                 // normalize separator to "-"
   144                 String tp = type.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
   146                 String tp = type.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
   145                 // validate
   147                 // validate
   146                 StringTokenIterator itr = new StringTokenIterator(tp, LanguageTag.SEP);
   148                 StringTokenIterator itr = new StringTokenIterator(tp, LanguageTag.SEP);
   147                 while (!itr.isDone()) {
   149                 while (!itr.isDone()) {
   148                     String s = itr.current();
   150                     String s = itr.current();
   149                     if (!UnicodeLocaleExtension.isTypeSubtag(s)) {
   151                     if (!UnicodeLocaleExtension.isTypeSubtag(s)) {
   150                         throw new LocaleSyntaxException("Ill-formed Unicode locale keyword type: " + type, itr.currentStart());
   152                         throw new LocaleSyntaxException("Ill-formed Unicode locale keyword type: "
       
   153                                                         + type,
       
   154                                                         itr.currentStart());
   151                     }
   155                     }
   152                     itr.next();
   156                     itr.next();
   153                 }
   157                 }
   154             }
   158             }
   155             if (_ukeywords == null) {
   159             if (ukeywords == null) {
   156                 _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
   160                 ukeywords = new HashMap<>(4);
   157             }
   161             }
   158             _ukeywords.put(cikey, type);
   162             ukeywords.put(cikey, type);
   159         }
   163         }
   160         return this;
   164         return this;
   161     }
   165     }
   162 
   166 
   163     public InternalLocaleBuilder setExtension(char singleton, String value) throws LocaleSyntaxException {
   167     public InternalLocaleBuilder setExtension(char singleton, String value) throws LocaleSyntaxException {
   165         boolean isBcpPrivateuse = LanguageTag.isPrivateusePrefixChar(singleton);
   169         boolean isBcpPrivateuse = LanguageTag.isPrivateusePrefixChar(singleton);
   166         if (!isBcpPrivateuse && !LanguageTag.isExtensionSingletonChar(singleton)) {
   170         if (!isBcpPrivateuse && !LanguageTag.isExtensionSingletonChar(singleton)) {
   167             throw new LocaleSyntaxException("Ill-formed extension key: " + singleton);
   171             throw new LocaleSyntaxException("Ill-formed extension key: " + singleton);
   168         }
   172         }
   169 
   173 
   170         boolean remove = (value == null || value.length() == 0);
   174         boolean remove = LocaleUtils.isEmpty(value);
   171         CaseInsensitiveChar key = new CaseInsensitiveChar(singleton);
   175         CaseInsensitiveChar key = new CaseInsensitiveChar(singleton);
   172 
   176 
   173         if (remove) {
   177         if (remove) {
   174             if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
   178             if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
   175                 // clear entire Unicode locale extension
   179                 // clear entire Unicode locale extension
   176                 if (_uattributes != null) {
   180                 if (uattributes != null) {
   177                     _uattributes.clear();
   181                     uattributes.clear();
   178                 }
   182                 }
   179                 if (_ukeywords != null) {
   183                 if (ukeywords != null) {
   180                     _ukeywords.clear();
   184                     ukeywords.clear();
   181                 }
   185                 }
   182             } else {
   186             } else {
   183                 if (_extensions != null && _extensions.containsKey(key)) {
   187                 if (extensions != null && extensions.containsKey(key)) {
   184                     _extensions.remove(key);
   188                     extensions.remove(key);
   185                 }
   189                 }
   186             }
   190             }
   187         } else {
   191         } else {
   188             // validate value
   192             // validate value
   189             String val = value.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
   193             String val = value.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
   195                     validSubtag = LanguageTag.isPrivateuseSubtag(s);
   199                     validSubtag = LanguageTag.isPrivateuseSubtag(s);
   196                 } else {
   200                 } else {
   197                     validSubtag = LanguageTag.isExtensionSubtag(s);
   201                     validSubtag = LanguageTag.isExtensionSubtag(s);
   198                 }
   202                 }
   199                 if (!validSubtag) {
   203                 if (!validSubtag) {
   200                     throw new LocaleSyntaxException("Ill-formed extension value: " + s, itr.currentStart());
   204                     throw new LocaleSyntaxException("Ill-formed extension value: " + s,
       
   205                                                     itr.currentStart());
   201                 }
   206                 }
   202                 itr.next();
   207                 itr.next();
   203             }
   208             }
   204 
   209 
   205             if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
   210             if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
   206                 setUnicodeLocaleExtension(val);
   211                 setUnicodeLocaleExtension(val);
   207             } else {
   212             } else {
   208                 if (_extensions == null) {
   213                 if (extensions == null) {
   209                     _extensions = new HashMap<CaseInsensitiveChar, String>(4);
   214                     extensions = new HashMap<>(4);
   210                 }
   215                 }
   211                 _extensions.put(key, val);
   216                 extensions.put(key, val);
   212             }
   217             }
   213         }
   218         }
   214         return this;
   219         return this;
   215     }
   220     }
   216 
   221 
   217     /*
   222     /*
   218      * Set extension/private subtags in a single string representation
   223      * Set extension/private subtags in a single string representation
   219      */
   224      */
   220     public InternalLocaleBuilder setExtensions(String subtags) throws LocaleSyntaxException {
   225     public InternalLocaleBuilder setExtensions(String subtags) throws LocaleSyntaxException {
   221         if (subtags == null || subtags.length() == 0) {
   226         if (LocaleUtils.isEmpty(subtags)) {
   222             clearExtensions();
   227             clearExtensions();
   223             return this;
   228             return this;
   224         }
   229         }
   225         subtags = subtags.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
   230         subtags = subtags.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
   226         StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
   231         StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
   250                     }
   255                     }
   251                     itr.next();
   256                     itr.next();
   252                 }
   257                 }
   253 
   258 
   254                 if (parsed < start) {
   259                 if (parsed < start) {
   255                     throw new LocaleSyntaxException("Incomplete extension '" + singleton + "'", start);
   260                     throw new LocaleSyntaxException("Incomplete extension '" + singleton + "'",
       
   261                                                     start);
   256                 }
   262                 }
   257 
   263 
   258                 if (extensions == null) {
   264                 if (extensions == null) {
   259                     extensions = new ArrayList<String>(4);
   265                     extensions = new ArrayList<>(4);
   260                 }
   266                 }
   261                 extensions.add(sb.toString());
   267                 extensions.add(sb.toString());
   262             } else {
   268             } else {
   263                 break;
   269                 break;
   264             }
   270             }
   279                     parsed = itr.currentEnd();
   285                     parsed = itr.currentEnd();
   280 
   286 
   281                     itr.next();
   287                     itr.next();
   282                 }
   288                 }
   283                 if (parsed <= start) {
   289                 if (parsed <= start) {
   284                     throw new LocaleSyntaxException("Incomplete privateuse:" + subtags.substring(start), start);
   290                     throw new LocaleSyntaxException("Incomplete privateuse:"
       
   291                                                     + subtags.substring(start),
       
   292                                                     start);
   285                 } else {
   293                 } else {
   286                     privateuse = sb.toString();
   294                     privateuse = sb.toString();
   287                 }
   295                 }
   288             }
   296             }
   289         }
   297         }
   290 
   298 
   291         if (!itr.isDone()) {
   299         if (!itr.isDone()) {
   292             throw new LocaleSyntaxException("Ill-formed extension subtags:" + subtags.substring(itr.currentStart()), itr.currentStart());
   300             throw new LocaleSyntaxException("Ill-formed extension subtags:"
       
   301                                             + subtags.substring(itr.currentStart()),
       
   302                                             itr.currentStart());
   293         }
   303         }
   294 
   304 
   295         return setExtensions(extensions, privateuse);
   305         return setExtensions(extensions, privateuse);
   296     }
   306     }
   297 
   307 
   300      * BCP47 extensions are already validated and well-formed, but may contain duplicates
   310      * BCP47 extensions are already validated and well-formed, but may contain duplicates
   301      */
   311      */
   302     private InternalLocaleBuilder setExtensions(List<String> bcpExtensions, String privateuse) {
   312     private InternalLocaleBuilder setExtensions(List<String> bcpExtensions, String privateuse) {
   303         clearExtensions();
   313         clearExtensions();
   304 
   314 
   305         if (bcpExtensions != null && bcpExtensions.size() > 0) {
   315         if (!LocaleUtils.isEmpty(bcpExtensions)) {
   306             HashSet<CaseInsensitiveChar> processedExntensions = new HashSet<CaseInsensitiveChar>(bcpExtensions.size());
   316             Set<CaseInsensitiveChar> done = new HashSet<>(bcpExtensions.size());
   307             for (String bcpExt : bcpExtensions) {
   317             for (String bcpExt : bcpExtensions) {
   308                 CaseInsensitiveChar key = new CaseInsensitiveChar(bcpExt.charAt(0));
   318                 CaseInsensitiveChar key = new CaseInsensitiveChar(bcpExt);
   309                 // ignore duplicates
   319                 // ignore duplicates
   310                 if (!processedExntensions.contains(key)) {
   320                 if (!done.contains(key)) {
   311                     // each extension string contains singleton, e.g. "a-abc-def"
   321                     // each extension string contains singleton, e.g. "a-abc-def"
   312                     if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
   322                     if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
   313                         setUnicodeLocaleExtension(bcpExt.substring(2));
   323                         setUnicodeLocaleExtension(bcpExt.substring(2));
   314                     } else {
   324                     } else {
   315                         if (_extensions == null) {
   325                         if (extensions == null) {
   316                             _extensions = new HashMap<CaseInsensitiveChar, String>(4);
   326                             extensions = new HashMap<>(4);
   317                         }
   327                         }
   318                         _extensions.put(key, bcpExt.substring(2));
   328                         extensions.put(key, bcpExt.substring(2));
   319                     }
   329                     }
   320                 }
   330                 }
       
   331                 done.add(key);
   321             }
   332             }
   322         }
   333         }
   323         if (privateuse != null && privateuse.length() > 0) {
   334         if (privateuse != null && privateuse.length() > 0) {
   324             // privateuse string contains prefix, e.g. "x-abc-def"
   335             // privateuse string contains prefix, e.g. "x-abc-def"
   325             if (_extensions == null) {
   336             if (extensions == null) {
   326                 _extensions = new HashMap<CaseInsensitiveChar, String>(1);
   337                 extensions = new HashMap<>(1);
   327             }
   338             }
   328             _extensions.put(new CaseInsensitiveChar(privateuse.charAt(0)), privateuse.substring(2));
   339             extensions.put(new CaseInsensitiveChar(privateuse), privateuse.substring(2));
   329         }
   340         }
   330 
   341 
   331         return this;
   342         return this;
   332     }
   343     }
   333 
   344 
   334     /*
   345     /*
   335      * Reset Builder's internal state with the given language tag
   346      * Reset Builder's internal state with the given language tag
   336      */
   347      */
   337     public InternalLocaleBuilder setLanguageTag(LanguageTag langtag) {
   348     public InternalLocaleBuilder setLanguageTag(LanguageTag langtag) {
   338         clear();
   349         clear();
   339         if (langtag.getExtlangs().size() > 0) {
   350         if (!langtag.getExtlangs().isEmpty()) {
   340             _language = langtag.getExtlangs().get(0);
   351             language = langtag.getExtlangs().get(0);
   341         } else {
   352         } else {
   342             String language = langtag.getLanguage();
   353             String lang = langtag.getLanguage();
   343             if (!language.equals(LanguageTag.UNDETERMINED)) {
   354             if (!lang.equals(LanguageTag.UNDETERMINED)) {
   344                 _language = language;
   355                 language = lang;
   345             }
   356             }
   346         }
   357         }
   347         _script = langtag.getScript();
   358         script = langtag.getScript();
   348         _region = langtag.getRegion();
   359         region = langtag.getRegion();
   349 
   360 
   350         List<String> bcpVariants = langtag.getVariants();
   361         List<String> bcpVariants = langtag.getVariants();
   351         if (bcpVariants.size() > 0) {
   362         if (!bcpVariants.isEmpty()) {
   352             StringBuilder var = new StringBuilder(bcpVariants.get(0));
   363             StringBuilder var = new StringBuilder(bcpVariants.get(0));
   353             for (int i = 1; i < bcpVariants.size(); i++) {
   364             int size = bcpVariants.size();
       
   365             for (int i = 1; i < size; i++) {
   354                 var.append(BaseLocale.SEP).append(bcpVariants.get(i));
   366                 var.append(BaseLocale.SEP).append(bcpVariants.get(i));
   355             }
   367             }
   356             _variant = var.toString();
   368             variant = var.toString();
   357         }
   369         }
   358 
   370 
   359         setExtensions(langtag.getExtensions(), langtag.getPrivateuse());
   371         setExtensions(langtag.getExtensions(), langtag.getPrivateuse());
   360 
   372 
   361         return this;
   373         return this;
   362     }
   374     }
   363 
   375 
   364     public InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions extensions) throws LocaleSyntaxException {
   376     public InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions localeExtensions) throws LocaleSyntaxException {
   365         String language = base.getLanguage();
   377         String language = base.getLanguage();
   366         String script = base.getScript();
   378         String script = base.getScript();
   367         String region = base.getRegion();
   379         String region = base.getRegion();
   368         String variant = base.getVariant();
   380         String variant = base.getVariant();
   369 
   381 
   371 
   383 
   372         // Exception 1 - ja_JP_JP
   384         // Exception 1 - ja_JP_JP
   373         if (language.equals("ja") && region.equals("JP") && variant.equals("JP")) {
   385         if (language.equals("ja") && region.equals("JP") && variant.equals("JP")) {
   374             // When locale ja_JP_JP is created, ca-japanese is always there.
   386             // When locale ja_JP_JP is created, ca-japanese is always there.
   375             // The builder ignores the variant "JP"
   387             // The builder ignores the variant "JP"
   376             assert("japanese".equals(extensions.getUnicodeLocaleType("ca")));
   388             assert("japanese".equals(localeExtensions.getUnicodeLocaleType("ca")));
   377             variant = "";
   389             variant = "";
   378         }
   390         }
   379         // Exception 2 - th_TH_TH
   391         // Exception 2 - th_TH_TH
   380         else if (language.equals("th") && region.equals("TH") && variant.equals("TH")) {
   392         else if (language.equals("th") && region.equals("TH") && variant.equals("TH")) {
   381             // When locale th_TH_TH is created, nu-thai is always there.
   393             // When locale th_TH_TH is created, nu-thai is always there.
   382             // The builder ignores the variant "TH"
   394             // The builder ignores the variant "TH"
   383             assert("thai".equals(extensions.getUnicodeLocaleType("nu")));
   395             assert("thai".equals(localeExtensions.getUnicodeLocaleType("nu")));
   384             variant = "";
   396             variant = "";
   385         }
   397         }
   386         // Exception 3 - no_NO_NY
   398         // Exception 3 - no_NO_NY
   387         else if (language.equals("no") && region.equals("NO") && variant.equals("NY")) {
   399         else if (language.equals("no") && region.equals("NO") && variant.equals("NY")) {
   388             // no_NO_NY is a valid locale and used by Java 6 or older versions.
   400             // no_NO_NY is a valid locale and used by Java 6 or older versions.
   413             }
   425             }
   414         }
   426         }
   415 
   427 
   416         // The input locale is validated at this point.
   428         // The input locale is validated at this point.
   417         // Now, updating builder's internal fields.
   429         // Now, updating builder's internal fields.
   418         _language = language;
   430         this.language = language;
   419         _script = script;
   431         this.script = script;
   420         _region = region;
   432         this.region = region;
   421         _variant = variant;
   433         this.variant = variant;
   422         clearExtensions();
   434         clearExtensions();
   423 
   435 
   424         Set<Character> extKeys = (extensions == null) ? null : extensions.getKeys();
   436         Set<Character> extKeys = (localeExtensions == null) ? null : localeExtensions.getKeys();
   425         if (extKeys != null) {
   437         if (extKeys != null) {
   426             // map extensions back to builder's internal format
   438             // map localeExtensions back to builder's internal format
   427             for (Character key : extKeys) {
   439             for (Character key : extKeys) {
   428                 Extension e = extensions.getExtension(key);
   440                 Extension e = localeExtensions.getExtension(key);
   429                 if (e instanceof UnicodeLocaleExtension) {
   441                 if (e instanceof UnicodeLocaleExtension) {
   430                     UnicodeLocaleExtension ue = (UnicodeLocaleExtension)e;
   442                     UnicodeLocaleExtension ue = (UnicodeLocaleExtension)e;
   431                     for (String uatr : ue.getUnicodeLocaleAttributes()) {
   443                     for (String uatr : ue.getUnicodeLocaleAttributes()) {
   432                         if (_uattributes == null) {
   444                         if (uattributes == null) {
   433                             _uattributes = new HashSet<CaseInsensitiveString>(4);
   445                             uattributes = new HashSet<>(4);
   434                         }
   446                         }
   435                         _uattributes.add(new CaseInsensitiveString(uatr));
   447                         uattributes.add(new CaseInsensitiveString(uatr));
   436                     }
   448                     }
   437                     for (String ukey : ue.getUnicodeLocaleKeys()) {
   449                     for (String ukey : ue.getUnicodeLocaleKeys()) {
   438                         if (_ukeywords == null) {
   450                         if (ukeywords == null) {
   439                             _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
   451                             ukeywords = new HashMap<>(4);
   440                         }
   452                         }
   441                         _ukeywords.put(new CaseInsensitiveString(ukey), ue.getUnicodeLocaleType(ukey));
   453                         ukeywords.put(new CaseInsensitiveString(ukey), ue.getUnicodeLocaleType(ukey));
   442                     }
   454                     }
   443                 } else {
   455                 } else {
   444                     if (_extensions == null) {
   456                     if (extensions == null) {
   445                         _extensions = new HashMap<CaseInsensitiveChar, String>(4);
   457                         extensions = new HashMap<>(4);
   446                     }
   458                     }
   447                     _extensions.put(new CaseInsensitiveChar(key.charValue()), e.getValue());
   459                     extensions.put(new CaseInsensitiveChar(key), e.getValue());
   448                 }
   460                 }
   449             }
   461             }
   450         }
   462         }
   451         return this;
   463         return this;
   452     }
   464     }
   453 
   465 
   454     public InternalLocaleBuilder clear() {
   466     public InternalLocaleBuilder clear() {
   455         _language = "";
   467         language = "";
   456         _script = "";
   468         script = "";
   457         _region = "";
   469         region = "";
   458         _variant = "";
   470         variant = "";
   459         clearExtensions();
   471         clearExtensions();
   460         return this;
   472         return this;
   461     }
   473     }
   462 
   474 
   463     public InternalLocaleBuilder clearExtensions() {
   475     public InternalLocaleBuilder clearExtensions() {
   464         if (_extensions != null) {
   476         if (extensions != null) {
   465             _extensions.clear();
   477             extensions.clear();
   466         }
   478         }
   467         if (_uattributes != null) {
   479         if (uattributes != null) {
   468             _uattributes.clear();
   480             uattributes.clear();
   469         }
   481         }
   470         if (_ukeywords != null) {
   482         if (ukeywords != null) {
   471             _ukeywords.clear();
   483             ukeywords.clear();
   472         }
   484         }
   473         return this;
   485         return this;
   474     }
   486     }
   475 
   487 
   476     public BaseLocale getBaseLocale() {
   488     public BaseLocale getBaseLocale() {
   477         String language = _language;
   489         String language = this.language;
   478         String script = _script;
   490         String script = this.script;
   479         String region = _region;
   491         String region = this.region;
   480         String variant = _variant;
   492         String variant = this.variant;
   481 
   493 
   482         // Special private use subtag sequence identified by "lvariant" will be
   494         // Special private use subtag sequence identified by "lvariant" will be
   483         // interpreted as Java variant.
   495         // interpreted as Java variant.
   484         if (_extensions != null) {
   496         if (extensions != null) {
   485             String privuse = _extensions.get(PRIVUSE_KEY);
   497             String privuse = extensions.get(PRIVATEUSE_KEY);
   486             if (privuse != null) {
   498             if (privuse != null) {
   487                 StringTokenIterator itr = new StringTokenIterator(privuse, LanguageTag.SEP);
   499                 StringTokenIterator itr = new StringTokenIterator(privuse, LanguageTag.SEP);
   488                 boolean sawPrefix = false;
   500                 boolean sawPrefix = false;
   489                 int privVarStart = -1;
   501                 int privVarStart = -1;
   490                 while (!itr.isDone()) {
   502                 while (!itr.isDone()) {
   491                     if (sawPrefix) {
   503                     if (sawPrefix) {
   492                         privVarStart = itr.currentStart();
   504                         privVarStart = itr.currentStart();
   493                         break;
   505                         break;
   494                     }
   506                     }
   495                     if (AsciiUtil.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
   507                     if (LocaleUtils.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
   496                         sawPrefix = true;
   508                         sawPrefix = true;
   497                     }
   509                     }
   498                     itr.next();
   510                     itr.next();
   499                 }
   511                 }
   500                 if (privVarStart != -1) {
   512                 if (privVarStart != -1) {
   501                     StringBuilder sb = new StringBuilder(variant);
   513                     StringBuilder sb = new StringBuilder(variant);
   502                     if (sb.length() != 0) {
   514                     if (sb.length() != 0) {
   503                         sb.append(BaseLocale.SEP);
   515                         sb.append(BaseLocale.SEP);
   504                     }
   516                     }
   505                     sb.append(privuse.substring(privVarStart).replaceAll(LanguageTag.SEP, BaseLocale.SEP));
   517                     sb.append(privuse.substring(privVarStart).replaceAll(LanguageTag.SEP,
       
   518                                                                          BaseLocale.SEP));
   506                     variant = sb.toString();
   519                     variant = sb.toString();
   507                 }
   520                 }
   508             }
   521             }
   509         }
   522         }
   510 
   523 
   511         return BaseLocale.getInstance(language, script, region, variant);
   524         return BaseLocale.getInstance(language, script, region, variant);
   512     }
   525     }
   513 
   526 
   514     public LocaleExtensions getLocaleExtensions() {
   527     public LocaleExtensions getLocaleExtensions() {
   515         if ((_extensions == null || _extensions.size() == 0)
   528         if (LocaleUtils.isEmpty(extensions) && LocaleUtils.isEmpty(uattributes)
   516                 && (_uattributes == null || _uattributes.size() == 0)
   529             && LocaleUtils.isEmpty(ukeywords)) {
   517                 && (_ukeywords == null || _ukeywords.size() == 0)) {
   530             return null;
   518             return LocaleExtensions.EMPTY_EXTENSIONS;
   531         }
   519         }
   532 
   520 
   533         LocaleExtensions lext = new LocaleExtensions(extensions, uattributes, ukeywords);
   521         return new LocaleExtensions(_extensions, _uattributes, _ukeywords);
   534         return lext.isEmpty() ? null : lext;
   522     }
   535     }
   523 
   536 
   524     /*
   537     /*
   525      * Remove special private use subtag sequence identified by "lvariant"
   538      * Remove special private use subtag sequence identified by "lvariant"
   526      * and return the rest. Only used by LocaleExtensions
   539      * and return the rest. Only used by LocaleExtensions
   538                 // Note: privateuse value "abc-lvariant" is unchanged
   551                 // Note: privateuse value "abc-lvariant" is unchanged
   539                 // because no subtags after "lvariant".
   552                 // because no subtags after "lvariant".
   540                 sawPrivuseVar = true;
   553                 sawPrivuseVar = true;
   541                 break;
   554                 break;
   542             }
   555             }
   543             if (AsciiUtil.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
   556             if (LocaleUtils.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
   544                 prefixStart = itr.currentStart();
   557                 prefixStart = itr.currentStart();
   545             }
   558             }
   546             itr.next();
   559             itr.next();
   547         }
   560         }
   548         if (!sawPrivuseVar) {
   561         if (!sawPrivuseVar) {
   574      * Duplicated attributes/keywords will be ignored.
   587      * Duplicated attributes/keywords will be ignored.
   575      * The input must be a valid extension subtags (excluding singleton).
   588      * The input must be a valid extension subtags (excluding singleton).
   576      */
   589      */
   577     private void setUnicodeLocaleExtension(String subtags) {
   590     private void setUnicodeLocaleExtension(String subtags) {
   578         // wipe out existing attributes/keywords
   591         // wipe out existing attributes/keywords
   579         if (_uattributes != null) {
   592         if (uattributes != null) {
   580             _uattributes.clear();
   593             uattributes.clear();
   581         }
   594         }
   582         if (_ukeywords != null) {
   595         if (ukeywords != null) {
   583             _ukeywords.clear();
   596             ukeywords.clear();
   584         }
   597         }
   585 
   598 
   586         StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
   599         StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
   587 
   600 
   588         // parse attributes
   601         // parse attributes
   589         while (!itr.isDone()) {
   602         while (!itr.isDone()) {
   590             if (!UnicodeLocaleExtension.isAttribute(itr.current())) {
   603             if (!UnicodeLocaleExtension.isAttribute(itr.current())) {
   591                 break;
   604                 break;
   592             }
   605             }
   593             if (_uattributes == null) {
   606             if (uattributes == null) {
   594                 _uattributes = new HashSet<CaseInsensitiveString>(4);
   607                 uattributes = new HashSet<>(4);
   595             }
   608             }
   596             _uattributes.add(new CaseInsensitiveString(itr.current()));
   609             uattributes.add(new CaseInsensitiveString(itr.current()));
   597             itr.next();
   610             itr.next();
   598         }
   611         }
   599 
   612 
   600         // parse keywords
   613         // parse keywords
   601         CaseInsensitiveString key = null;
   614         CaseInsensitiveString key = null;
   606             if (key != null) {
   619             if (key != null) {
   607                 if (UnicodeLocaleExtension.isKey(itr.current())) {
   620                 if (UnicodeLocaleExtension.isKey(itr.current())) {
   608                     // next keyword - emit previous one
   621                     // next keyword - emit previous one
   609                     assert(typeStart == -1 || typeEnd != -1);
   622                     assert(typeStart == -1 || typeEnd != -1);
   610                     type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
   623                     type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
   611                     if (_ukeywords == null) {
   624                     if (ukeywords == null) {
   612                         _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
   625                         ukeywords = new HashMap<>(4);
   613                     }
   626                     }
   614                     _ukeywords.put(key, type);
   627                     ukeywords.put(key, type);
   615 
   628 
   616                     // reset keyword info
   629                     // reset keyword info
   617                     CaseInsensitiveString tmpKey = new CaseInsensitiveString(itr.current());
   630                     CaseInsensitiveString tmpKey = new CaseInsensitiveString(itr.current());
   618                     key = _ukeywords.containsKey(tmpKey) ? null : tmpKey;
   631                     key = ukeywords.containsKey(tmpKey) ? null : tmpKey;
   619                     typeStart = typeEnd = -1;
   632                     typeStart = typeEnd = -1;
   620                 } else {
   633                 } else {
   621                     if (typeStart == -1) {
   634                     if (typeStart == -1) {
   622                         typeStart = itr.currentStart();
   635                         typeStart = itr.currentStart();
   623                     }
   636                     }
   625                 }
   638                 }
   626             } else if (UnicodeLocaleExtension.isKey(itr.current())) {
   639             } else if (UnicodeLocaleExtension.isKey(itr.current())) {
   627                 // 1. first keyword or
   640                 // 1. first keyword or
   628                 // 2. next keyword, but previous one was duplicate
   641                 // 2. next keyword, but previous one was duplicate
   629                 key = new CaseInsensitiveString(itr.current());
   642                 key = new CaseInsensitiveString(itr.current());
   630                 if (_ukeywords != null && _ukeywords.containsKey(key)) {
   643                 if (ukeywords != null && ukeywords.containsKey(key)) {
   631                     // duplicate
   644                     // duplicate
   632                     key = null;
   645                     key = null;
   633                 }
   646                 }
   634             }
   647             }
   635 
   648 
   636             if (!itr.hasNext()) {
   649             if (!itr.hasNext()) {
   637                 if (key != null) {
   650                 if (key != null) {
   638                     // last keyword
   651                     // last keyword
   639                     assert(typeStart == -1 || typeEnd != -1);
   652                     assert(typeStart == -1 || typeEnd != -1);
   640                     type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
   653                     type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
   641                     if (_ukeywords == null) {
   654                     if (ukeywords == null) {
   642                         _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
   655                         ukeywords = new HashMap<>(4);
   643                     }
   656                     }
   644                     _ukeywords.put(key, type);
   657                     ukeywords.put(key, type);
   645                 }
   658                 }
   646                 break;
   659                 break;
   647             }
   660             }
   648 
   661 
   649             itr.next();
   662             itr.next();
   650         }
   663         }
   651     }
   664     }
   652 
   665 
   653     static class CaseInsensitiveString {
   666     static final class CaseInsensitiveString {
   654         private String _s;
   667         private final String str, lowerStr;
   655 
   668 
   656         CaseInsensitiveString(String s) {
   669         CaseInsensitiveString(String s) {
   657             _s = s;
   670             str = s;
       
   671             lowerStr = LocaleUtils.toLowerString(s);
   658         }
   672         }
   659 
   673 
   660         public String value() {
   674         public String value() {
   661             return _s;
   675             return str;
   662         }
   676         }
   663 
   677 
       
   678         @Override
   664         public int hashCode() {
   679         public int hashCode() {
   665             return AsciiUtil.toLowerString(_s).hashCode();
   680             return lowerStr.hashCode();
   666         }
   681         }
   667 
   682 
       
   683         @Override
   668         public boolean equals(Object obj) {
   684         public boolean equals(Object obj) {
   669             if (this == obj) {
   685             if (this == obj) {
   670                 return true;
   686                 return true;
   671             }
   687             }
   672             if (!(obj instanceof CaseInsensitiveString)) {
   688             if (!(obj instanceof CaseInsensitiveString)) {
   673                 return false;
   689                 return false;
   674             }
   690             }
   675             return AsciiUtil.caseIgnoreMatch(_s, ((CaseInsensitiveString)obj).value());
   691             return lowerStr.equals(((CaseInsensitiveString)obj).lowerStr);
   676         }
   692         }
   677     }
   693     }
   678 
   694 
   679     static class CaseInsensitiveChar {
   695     static final class CaseInsensitiveChar {
   680         private char _c;
   696         private final char ch, lowerCh;
       
   697 
       
   698         /**
       
   699          * Constructs a CaseInsensitiveChar with the first char of the
       
   700          * given s.
       
   701          */
       
   702         private CaseInsensitiveChar(String s) {
       
   703             this(s.charAt(0));
       
   704         }
   681 
   705 
   682         CaseInsensitiveChar(char c) {
   706         CaseInsensitiveChar(char c) {
   683             _c = c;
   707             ch = c;
       
   708             lowerCh = LocaleUtils.toLower(ch);
   684         }
   709         }
   685 
   710 
   686         public char value() {
   711         public char value() {
   687             return _c;
   712             return ch;
   688         }
   713         }
   689 
   714 
       
   715         @Override
   690         public int hashCode() {
   716         public int hashCode() {
   691             return AsciiUtil.toLower(_c);
   717             return lowerCh;
   692         }
   718         }
   693 
   719 
       
   720         @Override
   694         public boolean equals(Object obj) {
   721         public boolean equals(Object obj) {
   695             if (this == obj) {
   722             if (this == obj) {
   696                 return true;
   723                 return true;
   697             }
   724             }
   698             if (!(obj instanceof CaseInsensitiveChar)) {
   725             if (!(obj instanceof CaseInsensitiveChar)) {
   699                 return false;
   726                 return false;
   700             }
   727             }
   701             return _c ==  AsciiUtil.toLower(((CaseInsensitiveChar)obj).value());
   728             return lowerCh == ((CaseInsensitiveChar)obj).lowerCh;
   702         }
   729         }
   703 
       
   704     }
   730     }
   705 }
   731 }