17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Copyright (C) 2009-2010, International Business Machines Corporation and    *
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * others. All Rights Reserved.                                                *
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.impl.locale;
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.ArrayList;
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashMap;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashSet;
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.List;
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Set;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic final class InternalLocaleBuilder {
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final boolean JDKIMPL = false;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String _language = "";
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String _script = "";
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String _region = "";
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String _variant = "";
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final CaseInsensitiveChar PRIVUSE_KEY = new CaseInsensitiveChar(LanguageTag.PRIVATEUSE.charAt(0));
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private HashMap<CaseInsensitiveChar, String> _extensions;
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private HashSet<CaseInsensitiveString> _uattributes;
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private HashMap<CaseInsensitiveString, String> _ukeywords;
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder() {
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setLanguage(String language) throws LocaleSyntaxException {
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (language == null || language.length() == 0) {
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _language = "";
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!LanguageTag.isLanguage(language)) {
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new LocaleSyntaxException("Ill-formed language: " + language, 0);
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _language = language;
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setScript(String script) throws LocaleSyntaxException {
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (script == null || script.length() == 0) {
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _script = "";
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!LanguageTag.isScript(script)) {
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new LocaleSyntaxException("Ill-formed script: " + script, 0);
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _script = script;
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setRegion(String region) throws LocaleSyntaxException {
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (region == null || region.length() == 0) {
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _region = "";
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!LanguageTag.isRegion(region)) {
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new LocaleSyntaxException("Ill-formed region: " + region, 0);
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _region = region;
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setVariant(String variant) throws LocaleSyntaxException {
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (variant == null || variant.length() == 0) {
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _variant = "";
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // normalize separators to "_"
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String var = variant.replaceAll(LanguageTag.SEP, BaseLocale.SEP);
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int errIdx = checkVariants(var, BaseLocale.SEP);
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (errIdx != -1) {
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _variant = var;
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder addUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Use case insensitive string to prevent duplication
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_uattributes == null) {
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _uattributes = new HashSet<CaseInsensitiveString>(4);
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _uattributes.add(new CaseInsensitiveString(attribute));
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder removeUnicodeLocaleAttribute(String attribute) throws LocaleSyntaxException {
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (attribute == null || !UnicodeLocaleExtension.isAttribute(attribute)) {
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed Unicode locale attribute: " + attribute);
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_uattributes != null) {
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _uattributes.remove(new CaseInsensitiveString(attribute));
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setUnicodeLocaleKeyword(String key, String type) throws LocaleSyntaxException {
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!UnicodeLocaleExtension.isKey(key)) {
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed Unicode locale keyword key: " + key);
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        CaseInsensitiveString cikey = new CaseInsensitiveString(key);
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (type == null) {
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (_ukeywords != null) {
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // null type is used for remove the key
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _ukeywords.remove(cikey);
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (type.length() != 0) {
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // normalize separator to "-"
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String tp = type.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // validate
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                StringTokenIterator itr = new StringTokenIterator(tp, LanguageTag.SEP);
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                while (!itr.isDone()) {
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String s = itr.current();
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (!UnicodeLocaleExtension.isTypeSubtag(s)) {
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        throw new LocaleSyntaxException("Ill-formed Unicode locale keyword type: " + type, itr.currentStart());
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    itr.next();
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (_ukeywords == null) {
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _ukeywords.put(cikey, type);
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setExtension(char singleton, String value) throws LocaleSyntaxException {
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // validate key
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        boolean isBcpPrivateuse = LanguageTag.isPrivateusePrefixChar(singleton);
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!isBcpPrivateuse && !LanguageTag.isExtensionSingletonChar(singleton)) {
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed extension key: " + singleton);
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        boolean remove = (value == null || value.length() == 0);
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        CaseInsensitiveChar key = new CaseInsensitiveChar(singleton);
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (remove) {
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // clear entire Unicode locale extension
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_uattributes != null) {
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _uattributes.clear();
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_ukeywords != null) {
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _ukeywords.clear();
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_extensions != null && _extensions.containsKey(key)) {
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _extensions.remove(key);
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // validate value
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String val = value.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringTokenIterator itr = new StringTokenIterator(val, LanguageTag.SEP);
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            while (!itr.isDone()) {
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String s = itr.current();
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                boolean validSubtag;
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (isBcpPrivateuse) {
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    validSubtag = LanguageTag.isPrivateuseSubtag(s);
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    validSubtag = LanguageTag.isExtensionSubtag(s);
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (!validSubtag) {
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new LocaleSyntaxException("Ill-formed extension value: " + s, itr.currentStart());
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                itr.next();
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                setUnicodeLocaleExtension(val);
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_extensions == null) {
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _extensions = new HashMap<CaseInsensitiveChar, String>(4);
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _extensions.put(key, val);
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Set extension/private subtags in a single string representation
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setExtensions(String subtags) throws LocaleSyntaxException {
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (subtags == null || subtags.length() == 0) {
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            clearExtensions();
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return this;
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        subtags = subtags.replaceAll(BaseLocale.SEP, LanguageTag.SEP);
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        List<String> extensions = null;
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String privateuse = null;
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int parsed = 0;
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int start;
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Make a list of extension subtags
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (!itr.isDone()) {
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String s = itr.current();
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (LanguageTag.isExtensionSingleton(s)) {
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                start = itr.currentStart();
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String singleton = s;
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                StringBuilder sb = new StringBuilder(singleton);
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                itr.next();
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                while (!itr.isDone()) {
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    s = itr.current();
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (LanguageTag.isExtensionSubtag(s)) {
2237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        sb.append(LanguageTag.SEP).append(s);
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        parsed = itr.currentEnd();
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        break;
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    itr.next();
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (parsed < start) {
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new LocaleSyntaxException("Incomplete extension '" + singleton + "'", start);
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (extensions == null) {
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    extensions = new ArrayList<String>(4);
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                extensions.add(sb.toString());
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!itr.isDone()) {
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String s = itr.current();
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (LanguageTag.isPrivateusePrefix(s)) {
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                start = itr.currentStart();
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                StringBuilder sb = new StringBuilder(s);
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                itr.next();
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                while (!itr.isDone()) {
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    s = itr.current();
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (!LanguageTag.isPrivateuseSubtag(s)) {
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        break;
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    sb.append(LanguageTag.SEP).append(s);
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    parsed = itr.currentEnd();
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    itr.next();
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (parsed <= start) {
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new LocaleSyntaxException("Incomplete privateuse:" + subtags.substring(start), start);
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    privateuse = sb.toString();
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!itr.isDone()) {
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed extension subtags:" + subtags.substring(itr.currentStart()), itr.currentStart());
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return setExtensions(extensions, privateuse);
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Set a list of BCP47 extensions and private use subtags
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * BCP47 extensions are already validated and well-formed, but may contain duplicates
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private InternalLocaleBuilder setExtensions(List<String> bcpExtensions, String privateuse) {
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        clearExtensions();
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (bcpExtensions != null && bcpExtensions.size() > 0) {
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            HashSet<CaseInsensitiveChar> processedExtensions = new HashSet<CaseInsensitiveChar>(bcpExtensions.size());
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String bcpExt : bcpExtensions) {
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                CaseInsensitiveChar key = new CaseInsensitiveChar(bcpExt.charAt(0));
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // ignore duplicates
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (!processedExtensions.contains(key)) {
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // each extension string contains singleton, e.g. "a-abc-def"
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (UnicodeLocaleExtension.isSingletonChar(key.value())) {
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        setUnicodeLocaleExtension(bcpExt.substring(2));
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (_extensions == null) {
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            _extensions = new HashMap<CaseInsensitiveChar, String>(4);
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        _extensions.put(key, bcpExt.substring(2));
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (privateuse != null && privateuse.length() > 0) {
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // privateuse string contains prefix, e.g. "x-abc-def"
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (_extensions == null) {
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _extensions = new HashMap<CaseInsensitiveChar, String>(1);
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _extensions.put(new CaseInsensitiveChar(privateuse.charAt(0)), privateuse.substring(2));
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Reset Builder's internal state with the given language tag
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setLanguageTag(LanguageTag langtag) {
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        clear();
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (langtag.getExtlangs().size() > 0) {
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _language = langtag.getExtlangs().get(0);
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String language = langtag.getLanguage();
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!language.equals(LanguageTag.UNDETERMINED)) {
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _language = language;
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _script = langtag.getScript();
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _region = langtag.getRegion();
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        List<String> bcpVariants = langtag.getVariants();
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (bcpVariants.size() > 0) {
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            StringBuilder var = new StringBuilder(bcpVariants.get(0));
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int i = 1; i < bcpVariants.size(); i++) {
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                var.append(BaseLocale.SEP).append(bcpVariants.get(i));
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _variant = var.toString();
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        setExtensions(langtag.getExtensions(), langtag.getPrivateuse());
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions extensions) throws LocaleSyntaxException {
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String language = base.getLanguage();
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String script = base.getScript();
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String region = base.getRegion();
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String variant = base.getVariant();
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (JDKIMPL) {
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Special backward compatibility support
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Exception 1 - ja_JP_JP
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (language.equals("ja") && region.equals("JP") && variant.equals("JP")) {
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // When locale ja_JP_JP is created, ca-japanese is always there.
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // The builder ignores the variant "JP"
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assert("japanese".equals(extensions.getUnicodeLocaleType("ca")));
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                variant = "";
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Exception 2 - th_TH_TH
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            else if (language.equals("th") && region.equals("TH") && variant.equals("TH")) {
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // When locale th_TH_TH is created, nu-thai is always there.
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // The builder ignores the variant "TH"
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assert("thai".equals(extensions.getUnicodeLocaleType("nu")));
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                variant = "";
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Exception 3 - no_NO_NY
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            else if (language.equals("no") && region.equals("NO") && variant.equals("NY")) {
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // no_NO_NY is a valid locale and used by Java 6 or older versions.
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // The build ignores the variant "NY" and change the language to "nn".
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                language = "nn";
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                variant = "";
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Validate base locale fields before updating internal state.
3747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // LocaleExtensions always store validated/canonicalized values,
3757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // so no checks are necessary.
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (language.length() > 0 && !LanguageTag.isLanguage(language)) {
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed language: " + language);
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (script.length() > 0 && !LanguageTag.isScript(script)) {
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed script: " + script);
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (region.length() > 0 && !LanguageTag.isRegion(region)) {
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new LocaleSyntaxException("Ill-formed region: " + region);
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (variant.length() > 0) {
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int errIdx = checkVariants(variant, BaseLocale.SEP);
3907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (errIdx != -1) {
3917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new LocaleSyntaxException("Ill-formed variant: " + variant, errIdx);
3927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // The input locale is validated at this point.
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Now, updating builder's internal fields.
3977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _language = language;
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _script = script;
3997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _region = region;
4007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _variant = variant;
4017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        clearExtensions();
4027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set<Character> extKeys = (extensions == null) ? null : extensions.getKeys();
4047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (extKeys != null) {
4057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // map extensions back to builder's internal format
4067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (Character key : extKeys) {
4077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Extension e = extensions.getExtension(key);
4087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (e instanceof UnicodeLocaleExtension) {
4097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    UnicodeLocaleExtension ue = (UnicodeLocaleExtension)e;
4107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    for (String uatr : ue.getUnicodeLocaleAttributes()) {
4117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (_uattributes == null) {
4127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            _uattributes = new HashSet<CaseInsensitiveString>(4);
4137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
4147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        _uattributes.add(new CaseInsensitiveString(uatr));
4157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    for (String ukey : ue.getUnicodeLocaleKeys()) {
4177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (_ukeywords == null) {
4187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
4197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
4207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        _ukeywords.put(new CaseInsensitiveString(ukey), ue.getUnicodeLocaleType(ukey));
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (_extensions == null) {
4247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        _extensions = new HashMap<CaseInsensitiveChar, String>(4);
4257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _extensions.put(new CaseInsensitiveChar(key.charValue()), e.getValue());
4277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
4287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
4317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder clear() {
4347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _language = "";
4357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _script = "";
4367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _region = "";
4377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        _variant = "";
4387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        clearExtensions();
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
4407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public InternalLocaleBuilder clearExtensions() {
4437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_extensions != null) {
4447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _extensions.clear();
4457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_uattributes != null) {
4477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _uattributes.clear();
4487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_ukeywords != null) {
4507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _ukeywords.clear();
4517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return this;
4537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public BaseLocale getBaseLocale() {
4567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String language = _language;
4577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String script = _script;
4587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String region = _region;
4597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String variant = _variant;
4607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Special private use subtag sequence identified by "lvariant" will be
4627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // interpreted as Java variant.
4637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_extensions != null) {
4647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String privuse = _extensions.get(PRIVUSE_KEY);
4657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (privuse != null) {
4667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                StringTokenIterator itr = new StringTokenIterator(privuse, LanguageTag.SEP);
4677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                boolean sawPrefix = false;
4687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int privVarStart = -1;
4697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                while (!itr.isDone()) {
4707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (sawPrefix) {
4717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        privVarStart = itr.currentStart();
4727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        break;
4737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (AsciiUtil.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
4757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        sawPrefix = true;
4767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    itr.next();
4787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
4797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (privVarStart != -1) {
4807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    StringBuilder sb = new StringBuilder(variant);
4817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (sb.length() != 0) {
4827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        sb.append(BaseLocale.SEP);
4837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
4847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    sb.append(privuse.substring(privVarStart).replaceAll(LanguageTag.SEP, BaseLocale.SEP));
4857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    variant = sb.toString();
4867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
4877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return BaseLocale.getInstance(language, script, region, variant);
4917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public LocaleExtensions getLocaleExtensions() {
4947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if ((_extensions == null || _extensions.size() == 0)
4957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && (_uattributes == null || _uattributes.size() == 0)
4967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && (_ukeywords == null || _ukeywords.size() == 0)) {
4977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return LocaleExtensions.EMPTY_EXTENSIONS;
4987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new LocaleExtensions(_extensions, _uattributes, _ukeywords);
5017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Remove special private use subtag sequence identified by "lvariant"
5057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and return the rest. Only used by LocaleExtensions
5067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static String removePrivateuseVariant(String privuseVal) {
5087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringTokenIterator itr = new StringTokenIterator(privuseVal, LanguageTag.SEP);
5097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Note: privateuse value "abc-lvariant" is unchanged
5117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // because no subtags after "lvariant".
5127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prefixStart = -1;
5147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        boolean sawPrivuseVar = false;
5157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (!itr.isDone()) {
5167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (prefixStart != -1) {
5177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Note: privateuse value "abc-lvariant" is unchanged
5187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // because no subtags after "lvariant".
5197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                sawPrivuseVar = true;
5207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
5217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (AsciiUtil.caseIgnoreMatch(itr.current(), LanguageTag.PRIVUSE_VARIANT_PREFIX)) {
5237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prefixStart = itr.currentStart();
5247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itr.next();
5267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!sawPrivuseVar) {
5287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return privuseVal;
5297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assert(prefixStart == 0 || prefixStart > 1);
5327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return (prefixStart == 0) ? null : privuseVal.substring(0, prefixStart -1);
5337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Check if the given variant subtags separated by the given
5377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * separator(s) are valid
5387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private int checkVariants(String variants, String sep) {
5407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringTokenIterator itr = new StringTokenIterator(variants, sep);
5417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (!itr.isDone()) {
5427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String s = itr.current();
5437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!LanguageTag.isVariant(s)) {
5447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return itr.currentStart();
5457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itr.next();
5477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return -1;
5497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Private methods parsing Unicode Locale Extension subtags.
5537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Duplicated attributes/keywords will be ignored.
5547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The input must be a valid extension subtags (excluding singleton).
5557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void setUnicodeLocaleExtension(String subtags) {
5577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // wipe out existing attributes/keywords
5587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_uattributes != null) {
5597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _uattributes.clear();
5607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (_ukeywords != null) {
5627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _ukeywords.clear();
5637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP);
5667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // parse attributes
5687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (!itr.isDone()) {
5697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!UnicodeLocaleExtension.isAttribute(itr.current())) {
5707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
5717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (_uattributes == null) {
5737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                _uattributes = new HashSet<CaseInsensitiveString>(4);
5747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _uattributes.add(new CaseInsensitiveString(itr.current()));
5767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itr.next();
5777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // parse keywords
5807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        CaseInsensitiveString key = null;
5817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String type;
5827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int typeStart = -1;
5837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int typeEnd = -1;
5847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (!itr.isDone()) {
5857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (key != null) {
5867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (UnicodeLocaleExtension.isKey(itr.current())) {
5877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // next keyword - emit previous one
5887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    assert(typeStart == -1 || typeEnd != -1);
5897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
5907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (_ukeywords == null) {
5917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
5927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
5937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _ukeywords.put(key, type);
5947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // reset keyword info
5967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    CaseInsensitiveString tmpKey = new CaseInsensitiveString(itr.current());
5977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    key = _ukeywords.containsKey(tmpKey) ? null : tmpKey;
5987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    typeStart = typeEnd = -1;
5997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
6007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (typeStart == -1) {
6017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        typeStart = itr.currentStart();
6027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
6037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    typeEnd = itr.currentEnd();
6047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (UnicodeLocaleExtension.isKey(itr.current())) {
6067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // 1. first keyword or
6077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // 2. next keyword, but previous one was duplicate
6087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                key = new CaseInsensitiveString(itr.current());
6097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (_ukeywords != null && _ukeywords.containsKey(key)) {
6107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // duplicate
6117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    key = null;
6127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!itr.hasNext()) {
6167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (key != null) {
6177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // last keyword
6187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    assert(typeStart == -1 || typeEnd != -1);
6197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    type = (typeStart == -1) ? "" : subtags.substring(typeStart, typeEnd);
6207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (_ukeywords == null) {
6217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        _ukeywords = new HashMap<CaseInsensitiveString, String>(4);
6227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
6237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    _ukeywords.put(key, type);
6247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
6267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            itr.next();
6297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static class CaseInsensitiveString {
6337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private String _s;
6347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        CaseInsensitiveString(String s) {
6367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _s = s;
6377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String value() {
6407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _s;
6417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int hashCode() {
6447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return AsciiUtil.toLowerString(_s).hashCode();
6457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean equals(Object obj) {
6487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (this == obj) {
6497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return true;
6507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!(obj instanceof CaseInsensitiveString)) {
6527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
6537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return AsciiUtil.caseIgnoreMatch(_s, ((CaseInsensitiveString)obj).value());
6557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static class CaseInsensitiveChar {
6597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private char _c;
6607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        CaseInsensitiveChar(char c) {
6627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            _c = c;
6637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public char value() {
6667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _c;
6677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int hashCode() {
6707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return AsciiUtil.toLower(_c);
6717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean equals(Object obj) {
6747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (this == obj) {
6757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return true;
6767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!(obj instanceof CaseInsensitiveChar)) {
6787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return false;
6797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return _c ==  AsciiUtil.toLower(((CaseInsensitiveChar)obj).value());
6817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
685