Home | History | Annotate | Download | only in impl
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /*
      4  *******************************************************************************
      5  * Copyright (C) 2009-2016, International Business Machines Corporation and
      6  * others. All Rights Reserved.
      7  *******************************************************************************
      8  */
      9 package com.ibm.icu.impl;
     10 
     11 import java.util.ArrayList;
     12 import java.util.Collections;
     13 import java.util.Comparator;
     14 import java.util.HashMap;
     15 import java.util.HashSet;
     16 import java.util.Iterator;
     17 import java.util.List;
     18 import java.util.Locale;
     19 import java.util.Map;
     20 import java.util.Map.Entry;
     21 import java.util.MissingResourceException;
     22 import java.util.Set;
     23 
     24 import com.ibm.icu.impl.CurrencyData.CurrencyDisplayInfo;
     25 import com.ibm.icu.impl.locale.AsciiUtil;
     26 import com.ibm.icu.lang.UCharacter;
     27 import com.ibm.icu.lang.UScript;
     28 import com.ibm.icu.text.BreakIterator;
     29 import com.ibm.icu.text.CaseMap;
     30 import com.ibm.icu.text.DisplayContext;
     31 import com.ibm.icu.text.DisplayContext.Type;
     32 import com.ibm.icu.text.LocaleDisplayNames;
     33 import com.ibm.icu.util.ULocale;
     34 import com.ibm.icu.util.UResourceBundle;
     35 
     36 public class LocaleDisplayNamesImpl extends LocaleDisplayNames {
     37     private final ULocale locale;
     38     private final DialectHandling dialectHandling;
     39     private final DisplayContext capitalization;
     40     private final DisplayContext nameLength;
     41     private final DisplayContext substituteHandling;
     42     private final DataTable langData;
     43     private final DataTable regionData;
     44     // Compiled SimpleFormatter patterns.
     45     private final String separatorFormat;
     46     private final String format;
     47     private final String keyTypeFormat;
     48     private final char formatOpenParen;
     49     private final char formatReplaceOpenParen;
     50     private final char formatCloseParen;
     51     private final char formatReplaceCloseParen;
     52     private final CurrencyDisplayInfo currencyDisplayInfo;
     53 
     54     private static final Cache cache = new Cache();
     55 
     56     /**
     57      * Capitalization context usage types for locale display names
     58      */
     59     private enum CapitalizationContextUsage {
     60         LANGUAGE,
     61         SCRIPT,
     62         TERRITORY,
     63         VARIANT,
     64         KEY,
     65         KEYVALUE
     66     }
     67     /**
     68      * Capitalization transforms. For each usage type, indicates whether to titlecase for
     69      * the context specified in capitalization (which we know at construction time).
     70      */
     71     private boolean[] capitalizationUsage = null;
     72     /**
     73      * Map from resource key to CapitalizationContextUsage value
     74      */
     75     private static final Map<String, CapitalizationContextUsage> contextUsageTypeMap;
     76     static {
     77         contextUsageTypeMap=new HashMap<String, CapitalizationContextUsage>();
     78         contextUsageTypeMap.put("languages", CapitalizationContextUsage.LANGUAGE);
     79         contextUsageTypeMap.put("script",    CapitalizationContextUsage.SCRIPT);
     80         contextUsageTypeMap.put("territory", CapitalizationContextUsage.TERRITORY);
     81         contextUsageTypeMap.put("variant",   CapitalizationContextUsage.VARIANT);
     82         contextUsageTypeMap.put("key",       CapitalizationContextUsage.KEY);
     83         contextUsageTypeMap.put("keyValue",  CapitalizationContextUsage.KEYVALUE);
     84     }
     85     /**
     86      * BreakIterator to use for capitalization
     87      */
     88     private transient BreakIterator capitalizationBrkIter = null;
     89 
     90     private static final CaseMap.Title TO_TITLE_WHOLE_STRING_NO_LOWERCASE =
     91             CaseMap.toTitle().wholeString().noLowercase();
     92 
     93     private static String toTitleWholeStringNoLowercase(ULocale locale, String s) {
     94         return TO_TITLE_WHOLE_STRING_NO_LOWERCASE.apply(
     95                 locale.toLocale(), null, s, new StringBuilder(), null).toString();
     96     }
     97 
     98     public static LocaleDisplayNames getInstance(ULocale locale, DialectHandling dialectHandling) {
     99         synchronized (cache) {
    100             return cache.get(locale, dialectHandling);
    101         }
    102     }
    103 
    104     public static LocaleDisplayNames getInstance(ULocale locale, DisplayContext... contexts) {
    105         synchronized (cache) {
    106             return cache.get(locale, contexts);
    107         }
    108     }
    109 
    110     private final class CapitalizationContextSink extends UResource.Sink {
    111         boolean hasCapitalizationUsage = false;
    112 
    113         @Override
    114         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
    115             UResource.Table contextsTable = value.getTable();
    116             for (int i = 0; contextsTable.getKeyAndValue(i, key, value); ++i) {
    117 
    118                 CapitalizationContextUsage usage = contextUsageTypeMap.get(key.toString());
    119                 if (usage == null) { continue; };
    120 
    121                 int[] intVector = value.getIntVector();
    122                 if (intVector.length < 2) { continue; }
    123 
    124                 int titlecaseInt = (capitalization == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)
    125                         ? intVector[0] : intVector[1];
    126                 if (titlecaseInt == 0) { continue; }
    127 
    128                 capitalizationUsage[usage.ordinal()] = true;
    129                 hasCapitalizationUsage = true;
    130             }
    131         }
    132     }
    133 
    134     public LocaleDisplayNamesImpl(ULocale locale, DialectHandling dialectHandling) {
    135         this(locale, (dialectHandling==DialectHandling.STANDARD_NAMES)? DisplayContext.STANDARD_NAMES: DisplayContext.DIALECT_NAMES,
    136                 DisplayContext.CAPITALIZATION_NONE);
    137     }
    138 
    139     public LocaleDisplayNamesImpl(ULocale locale, DisplayContext... contexts) {
    140         DialectHandling dialectHandling = DialectHandling.STANDARD_NAMES;
    141         DisplayContext capitalization = DisplayContext.CAPITALIZATION_NONE;
    142         DisplayContext nameLength = DisplayContext.LENGTH_FULL;
    143         DisplayContext substituteHandling = DisplayContext.SUBSTITUTE;
    144         for (DisplayContext contextItem : contexts) {
    145             switch (contextItem.type()) {
    146             case DIALECT_HANDLING:
    147                 dialectHandling = (contextItem.value()==DisplayContext.STANDARD_NAMES.value())?
    148                         DialectHandling.STANDARD_NAMES: DialectHandling.DIALECT_NAMES;
    149                 break;
    150             case CAPITALIZATION:
    151                 capitalization = contextItem;
    152                 break;
    153             case DISPLAY_LENGTH:
    154                 nameLength = contextItem;
    155                 break;
    156             case SUBSTITUTE_HANDLING:
    157                 substituteHandling = contextItem;
    158                 break;
    159             default:
    160                 break;
    161             }
    162         }
    163 
    164         this.dialectHandling = dialectHandling;
    165         this.capitalization = capitalization;
    166         this.nameLength = nameLength;
    167         this.substituteHandling = substituteHandling;
    168         this.langData = LangDataTables.impl.get(locale, substituteHandling == DisplayContext.NO_SUBSTITUTE);
    169         this.regionData = RegionDataTables.impl.get(locale, substituteHandling == DisplayContext.NO_SUBSTITUTE);
    170         this.locale = ULocale.ROOT.equals(langData.getLocale()) ? regionData.getLocale() :
    171             langData.getLocale();
    172 
    173         // Note, by going through DataTable, this uses table lookup rather than straight lookup.
    174         // That should get us the same data, I think.  This way we don't have to explicitly
    175         // load the bundle again.  Using direct lookup didn't seem to make an appreciable
    176         // difference in performance.
    177         String sep = langData.get("localeDisplayPattern", "separator");
    178         if (sep == null || "separator".equals(sep)) {
    179             sep = "{0}, {1}";
    180         }
    181         StringBuilder sb = new StringBuilder();
    182         this.separatorFormat = SimpleFormatterImpl.compileToStringMinMaxArguments(sep, sb, 2, 2);
    183 
    184         String pattern = langData.get("localeDisplayPattern", "pattern");
    185         if (pattern == null || "pattern".equals(pattern)) {
    186             pattern = "{0} ({1})";
    187         }
    188         this.format = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
    189         if (pattern.contains("")) {
    190             formatOpenParen = '';
    191             formatCloseParen = '';
    192             formatReplaceOpenParen = '';
    193             formatReplaceCloseParen = '';
    194         } else  {
    195             formatOpenParen = '(';
    196             formatCloseParen = ')';
    197             formatReplaceOpenParen = '[';
    198             formatReplaceCloseParen = ']';
    199         }
    200 
    201         String keyTypePattern = langData.get("localeDisplayPattern", "keyTypePattern");
    202         if (keyTypePattern == null || "keyTypePattern".equals(keyTypePattern)) {
    203             keyTypePattern = "{0}={1}";
    204         }
    205         this.keyTypeFormat = SimpleFormatterImpl.compileToStringMinMaxArguments(
    206                 keyTypePattern, sb, 2, 2);
    207 
    208         // Get values from the contextTransforms data if we need them
    209         // Also check whether we will need a break iterator (depends on the data)
    210         boolean needBrkIter = false;
    211         if (capitalization == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU ||
    212                 capitalization == DisplayContext.CAPITALIZATION_FOR_STANDALONE) {
    213             capitalizationUsage = new boolean[CapitalizationContextUsage.values().length]; // initialized to all false
    214             ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
    215             CapitalizationContextSink sink = new CapitalizationContextSink();
    216             try {
    217                 rb.getAllItemsWithFallback("contextTransforms", sink);
    218             }
    219             catch (MissingResourceException e) {
    220                 // Silently ignore.  Not every locale has contextTransforms.
    221             }
    222             needBrkIter = sink.hasCapitalizationUsage;
    223         }
    224         // Get a sentence break iterator if we will need it
    225         if (needBrkIter || capitalization == DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE) {
    226             capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
    227         }
    228 
    229         this.currencyDisplayInfo = CurrencyData.provider.getInstance(locale, false);
    230     }
    231 
    232     @Override
    233     public ULocale getLocale() {
    234         return locale;
    235     }
    236 
    237     @Override
    238     public DialectHandling getDialectHandling() {
    239         return dialectHandling;
    240     }
    241 
    242     @Override
    243     public DisplayContext getContext(DisplayContext.Type type) {
    244         DisplayContext result;
    245         switch (type) {
    246         case DIALECT_HANDLING:
    247             result = (dialectHandling==DialectHandling.STANDARD_NAMES)? DisplayContext.STANDARD_NAMES: DisplayContext.DIALECT_NAMES;
    248             break;
    249         case CAPITALIZATION:
    250             result = capitalization;
    251             break;
    252         case DISPLAY_LENGTH:
    253             result = nameLength;
    254             break;
    255         case SUBSTITUTE_HANDLING:
    256             result = substituteHandling;
    257             break;
    258         default:
    259             result = DisplayContext.STANDARD_NAMES; // hmm, we should do something else here
    260             break;
    261         }
    262         return result;
    263     }
    264 
    265     private String adjustForUsageAndContext(CapitalizationContextUsage usage, String name) {
    266         if (name != null && name.length() > 0 && UCharacter.isLowerCase(name.codePointAt(0)) &&
    267                 (capitalization==DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE ||
    268                 (capitalizationUsage != null && capitalizationUsage[usage.ordinal()]) )) {
    269             // Note, won't have capitalizationUsage != null && capitalizationUsage[usage.ordinal()]
    270             // unless capitalization is CAPITALIZATION_FOR_UI_LIST_OR_MENU or CAPITALIZATION_FOR_STANDALONE
    271             synchronized (this) {
    272                 if (capitalizationBrkIter == null) {
    273                     // should only happen when deserializing, etc.
    274                     capitalizationBrkIter = BreakIterator.getSentenceInstance(locale);
    275                 }
    276                 return UCharacter.toTitleCase(locale, name, capitalizationBrkIter,
    277                         UCharacter.TITLECASE_NO_LOWERCASE | UCharacter.TITLECASE_NO_BREAK_ADJUSTMENT);
    278             }
    279         }
    280         return name;
    281     }
    282 
    283     @Override
    284     public String localeDisplayName(ULocale locale) {
    285         return localeDisplayNameInternal(locale);
    286     }
    287 
    288     @Override
    289     public String localeDisplayName(Locale locale) {
    290         return localeDisplayNameInternal(ULocale.forLocale(locale));
    291     }
    292 
    293     @Override
    294     public String localeDisplayName(String localeId) {
    295         return localeDisplayNameInternal(new ULocale(localeId));
    296     }
    297 
    298     // TODO: implement use of capitalization
    299     private String localeDisplayNameInternal(ULocale locale) {
    300         // lang
    301         // lang (script, country, variant, keyword=value, ...)
    302         // script, country, variant, keyword=value, ...
    303 
    304         String resultName = null;
    305 
    306         String lang = locale.getLanguage();
    307 
    308         // Empty basename indicates root locale (keywords are ignored for this).
    309         // Our data uses 'root' to access display names for the root locale in the
    310         // "Languages" table.
    311         if (locale.getBaseName().length() == 0) {
    312             lang = "root";
    313         }
    314         String script = locale.getScript();
    315         String country = locale.getCountry();
    316         String variant = locale.getVariant();
    317 
    318         boolean hasScript = script.length() > 0;
    319         boolean hasCountry = country.length() > 0;
    320         boolean hasVariant = variant.length() > 0;
    321 
    322         // always have a value for lang
    323         if (dialectHandling == DialectHandling.DIALECT_NAMES) {
    324             do { // loop construct is so we can break early out of search
    325                 if (hasScript && hasCountry) {
    326                     String langScriptCountry = lang + '_' + script + '_' + country;
    327                     String result = localeIdName(langScriptCountry);
    328                     if (result != null && !result.equals(langScriptCountry)) {
    329                         resultName = result;
    330                         hasScript = false;
    331                         hasCountry = false;
    332                         break;
    333                     }
    334                 }
    335                 if (hasScript) {
    336                     String langScript = lang + '_' + script;
    337                     String result = localeIdName(langScript);
    338                     if (result != null && !result.equals(langScript)) {
    339                         resultName = result;
    340                         hasScript = false;
    341                         break;
    342                     }
    343                 }
    344                 if (hasCountry) {
    345                     String langCountry = lang + '_' + country;
    346                     String result = localeIdName(langCountry);
    347                     if (result != null && !result.equals(langCountry)) {
    348                         resultName = result;
    349                         hasCountry = false;
    350                         break;
    351                     }
    352                 }
    353             } while (false);
    354         }
    355 
    356         if (resultName == null) {
    357             String result = localeIdName(lang);
    358             if (result == null) { return null; }
    359             resultName = result
    360                     .replace(formatOpenParen, formatReplaceOpenParen)
    361                     .replace(formatCloseParen, formatReplaceCloseParen);
    362         }
    363 
    364         StringBuilder buf = new StringBuilder();
    365         if (hasScript) {
    366             // first element, don't need appendWithSep
    367             String result = scriptDisplayNameInContext(script, true);
    368             if (result == null) { return null; }
    369             buf.append(result
    370                     .replace(formatOpenParen, formatReplaceOpenParen)
    371                     .replace(formatCloseParen, formatReplaceCloseParen));
    372         }
    373         if (hasCountry) {
    374             String result = regionDisplayName(country, true);
    375             if (result == null) { return null; }
    376             appendWithSep(result
    377                     .replace(formatOpenParen, formatReplaceOpenParen)
    378                     .replace(formatCloseParen, formatReplaceCloseParen), buf);
    379         }
    380         if (hasVariant) {
    381             String result = variantDisplayName(variant, true);
    382             if (result == null) { return null; }
    383             appendWithSep(result
    384                     .replace(formatOpenParen, formatReplaceOpenParen)
    385                     .replace(formatCloseParen, formatReplaceCloseParen), buf);
    386         }
    387 
    388         Iterator<String> keys = locale.getKeywords();
    389         if (keys != null) {
    390             while (keys.hasNext()) {
    391                 String key = keys.next();
    392                 String value = locale.getKeywordValue(key);
    393                 String keyDisplayName = keyDisplayName(key, true);
    394                 if (keyDisplayName == null) { return null; }
    395                 keyDisplayName = keyDisplayName
    396                         .replace(formatOpenParen, formatReplaceOpenParen)
    397                         .replace(formatCloseParen, formatReplaceCloseParen);
    398                 String valueDisplayName = keyValueDisplayName(key, value, true);
    399                 if (valueDisplayName == null) { return null; }
    400                 valueDisplayName = valueDisplayName
    401                         .replace(formatOpenParen, formatReplaceOpenParen)
    402                         .replace(formatCloseParen, formatReplaceCloseParen);
    403                 if (!valueDisplayName.equals(value)) {
    404                     appendWithSep(valueDisplayName, buf);
    405                 } else if (!key.equals(keyDisplayName)) {
    406                     String keyValue = SimpleFormatterImpl.formatCompiledPattern(
    407                             keyTypeFormat, keyDisplayName, valueDisplayName);
    408                     appendWithSep(keyValue, buf);
    409                 } else {
    410                     appendWithSep(keyDisplayName, buf)
    411                     .append("=")
    412                     .append(valueDisplayName);
    413                 }
    414             }
    415         }
    416 
    417         String resultRemainder = null;
    418         if (buf.length() > 0) {
    419             resultRemainder = buf.toString();
    420         }
    421 
    422         if (resultRemainder != null) {
    423             resultName = SimpleFormatterImpl.formatCompiledPattern(
    424                     format, resultName, resultRemainder);
    425         }
    426 
    427         return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, resultName);
    428     }
    429 
    430     private String localeIdName(String localeId) {
    431         if (nameLength == DisplayContext.LENGTH_SHORT) {
    432             String locIdName = langData.get("Languages%short", localeId);
    433             if (locIdName != null && !locIdName.equals(localeId)) {
    434                 return locIdName;
    435             }
    436         }
    437         return langData.get("Languages", localeId);
    438     }
    439 
    440     @Override
    441     public String languageDisplayName(String lang) {
    442         // Special case to eliminate non-languages, which pollute our data.
    443         if (lang.equals("root") || lang.indexOf('_') != -1) {
    444             return substituteHandling == DisplayContext.SUBSTITUTE ? lang : null;
    445         }
    446         if (nameLength == DisplayContext.LENGTH_SHORT) {
    447             String langName = langData.get("Languages%short", lang);
    448             if (langName != null && !langName.equals(lang)) {
    449                 return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langName);
    450             }
    451         }
    452         return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langData.get("Languages", lang));
    453     }
    454 
    455     @Override
    456     public String scriptDisplayName(String script) {
    457         String str = langData.get("Scripts%stand-alone", script);
    458         if (str == null || str.equals(script)) {
    459             if (nameLength == DisplayContext.LENGTH_SHORT) {
    460                 str = langData.get("Scripts%short", script);
    461                 if (str != null && !str.equals(script)) {
    462                     return adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, str);
    463                 }
    464             }
    465             str = langData.get("Scripts", script);
    466         }
    467         return adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, str);
    468     }
    469 
    470     private String scriptDisplayNameInContext(String script, boolean skipAdjust) {
    471         if (nameLength == DisplayContext.LENGTH_SHORT) {
    472             String scriptName = langData.get("Scripts%short", script);
    473             if (scriptName != null && !scriptName.equals(script)) {
    474                 return skipAdjust? scriptName: adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, scriptName);
    475             }
    476         }
    477         String scriptName = langData.get("Scripts", script);
    478         return skipAdjust? scriptName: adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, scriptName);
    479     }
    480 
    481     @Override
    482     public String scriptDisplayNameInContext(String script) {
    483         return scriptDisplayNameInContext(script, false);
    484     }
    485 
    486     @Override
    487     public String scriptDisplayName(int scriptCode) {
    488         return scriptDisplayName(UScript.getShortName(scriptCode));
    489     }
    490 
    491     private String regionDisplayName(String region, boolean skipAdjust) {
    492         if (nameLength == DisplayContext.LENGTH_SHORT) {
    493             String regionName = regionData.get("Countries%short", region);
    494             if (regionName != null && !regionName.equals(region)) {
    495                 return skipAdjust? regionName: adjustForUsageAndContext(CapitalizationContextUsage.TERRITORY, regionName);
    496             }
    497         }
    498         String regionName = regionData.get("Countries", region);
    499         return skipAdjust? regionName: adjustForUsageAndContext(CapitalizationContextUsage.TERRITORY, regionName);
    500     }
    501 
    502     @Override
    503     public String regionDisplayName(String region) {
    504         return regionDisplayName(region, false);
    505     }
    506 
    507     private String variantDisplayName(String variant, boolean skipAdjust) {
    508         // don't have a resource for short variant names
    509         String variantName = langData.get("Variants", variant);
    510         return skipAdjust? variantName: adjustForUsageAndContext(CapitalizationContextUsage.VARIANT, variantName);
    511     }
    512 
    513     @Override
    514     public String variantDisplayName(String variant) {
    515         return variantDisplayName(variant, false);
    516     }
    517 
    518     private String keyDisplayName(String key, boolean skipAdjust) {
    519         // don't have a resource for short key names
    520         String keyName = langData.get("Keys", key);
    521         return skipAdjust? keyName: adjustForUsageAndContext(CapitalizationContextUsage.KEY, keyName);
    522     }
    523 
    524     @Override
    525     public String keyDisplayName(String key) {
    526         return keyDisplayName(key, false);
    527     }
    528 
    529     private String keyValueDisplayName(String key, String value, boolean skipAdjust) {
    530         String keyValueName = null;
    531 
    532         if (key.equals("currency")) {
    533             keyValueName = currencyDisplayInfo.getName(AsciiUtil.toUpperString(value));
    534             if (keyValueName == null) {
    535                 keyValueName = value;
    536             }
    537         } else {
    538             if (nameLength == DisplayContext.LENGTH_SHORT) {
    539                 String tmp = langData.get("Types%short", key, value);
    540                 if (tmp != null && !tmp.equals(value)) {
    541                     keyValueName = tmp;
    542                 }
    543             }
    544             if (keyValueName == null) {
    545                 keyValueName = langData.get("Types", key, value);
    546             }
    547         }
    548 
    549         return skipAdjust? keyValueName: adjustForUsageAndContext(CapitalizationContextUsage.KEYVALUE, keyValueName);
    550     }
    551 
    552     @Override
    553     public String keyValueDisplayName(String key, String value) {
    554         return keyValueDisplayName(key, value, false);
    555     }
    556 
    557     @Override
    558     public List<UiListItem> getUiListCompareWholeItems(Set<ULocale> localeSet, Comparator<UiListItem> comparator) {
    559         DisplayContext capContext = getContext(Type.CAPITALIZATION);
    560 
    561         List<UiListItem> result = new ArrayList<UiListItem>();
    562         Map<ULocale,Set<ULocale>> baseToLocales = new HashMap<ULocale,Set<ULocale>>();
    563         ULocale.Builder builder = new ULocale.Builder();
    564         for (ULocale locOriginal : localeSet) {
    565             builder.setLocale(locOriginal); // verify well-formed. We do this here so that we consistently throw exception
    566             ULocale loc = ULocale.addLikelySubtags(locOriginal);
    567             ULocale base = new ULocale(loc.getLanguage());
    568             Set<ULocale> locales = baseToLocales.get(base);
    569             if (locales == null) {
    570                 baseToLocales.put(base, locales = new HashSet<ULocale>());
    571             }
    572             locales.add(loc);
    573         }
    574         for (Entry<ULocale, Set<ULocale>> entry : baseToLocales.entrySet()) {
    575             ULocale base = entry.getKey();
    576             Set<ULocale> values = entry.getValue();
    577             if (values.size() == 1) {
    578                 ULocale locale = values.iterator().next();
    579                 result.add(newRow(ULocale.minimizeSubtags(locale, ULocale.Minimize.FAVOR_SCRIPT), capContext));
    580             } else {
    581                 Set<String> scripts = new HashSet<String>();
    582                 Set<String> regions = new HashSet<String>();
    583                 // need the follow two steps to make sure that unusual scripts or regions are displayed
    584                 ULocale maxBase = ULocale.addLikelySubtags(base);
    585                 scripts.add(maxBase.getScript());
    586                 regions.add(maxBase.getCountry());
    587                 for (ULocale locale : values) {
    588                     scripts.add(locale.getScript());
    589                     regions.add(locale.getCountry());
    590                 }
    591                 boolean hasScripts = scripts.size() > 1;
    592                 boolean hasRegions = regions.size() > 1;
    593                 for (ULocale locale : values) {
    594                     ULocale.Builder modified = builder.setLocale(locale);
    595                     if (!hasScripts) {
    596                         modified.setScript("");
    597                     }
    598                     if (!hasRegions) {
    599                         modified.setRegion("");
    600                     }
    601                     result.add(newRow(modified.build(), capContext));
    602                 }
    603             }
    604         }
    605         Collections.sort(result, comparator);
    606         return result;
    607     }
    608 
    609     private UiListItem newRow(ULocale modified, DisplayContext capContext) {
    610         ULocale minimized = ULocale.minimizeSubtags(modified, ULocale.Minimize.FAVOR_SCRIPT);
    611         String tempName = modified.getDisplayName(locale);
    612         boolean titlecase = capContext == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
    613         String nameInDisplayLocale =
    614                 titlecase ? toTitleWholeStringNoLowercase(locale, tempName) : tempName;
    615         tempName = modified.getDisplayName(modified);
    616         String nameInSelf = capContext ==
    617                 DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU ?
    618                         toTitleWholeStringNoLowercase(modified, tempName) : tempName;
    619         return new UiListItem(minimized, modified, nameInDisplayLocale, nameInSelf);
    620     }
    621 
    622     public static class DataTable {
    623         final boolean nullIfNotFound;
    624 
    625         DataTable(boolean nullIfNotFound) {
    626             this.nullIfNotFound = nullIfNotFound;
    627         }
    628 
    629         ULocale getLocale() {
    630             return ULocale.ROOT;
    631         }
    632 
    633         String get(String tableName, String code) {
    634             return get(tableName, null, code);
    635         }
    636 
    637         String get(String tableName, String subTableName, String code) {
    638             return nullIfNotFound ? null : code;
    639         }
    640     }
    641 
    642     static class ICUDataTable extends DataTable {
    643         private final ICUResourceBundle bundle;
    644 
    645         public ICUDataTable(String path, ULocale locale, boolean nullIfNotFound) {
    646             super(nullIfNotFound);
    647             this.bundle = (ICUResourceBundle) UResourceBundle.getBundleInstance(
    648                     path, locale.getBaseName());
    649         }
    650 
    651         @Override
    652         public ULocale getLocale() {
    653             return bundle.getULocale();
    654         }
    655 
    656         @Override
    657         public String get(String tableName, String subTableName, String code) {
    658             return ICUResourceTableAccess.getTableString(bundle, tableName, subTableName,
    659                     code, nullIfNotFound ? null : code);
    660         }
    661     }
    662 
    663     static abstract class DataTables {
    664         public abstract DataTable get(ULocale locale, boolean nullIfNotFound);
    665         public static DataTables load(String className) {
    666             try {
    667                 return (DataTables) Class.forName(className).newInstance();
    668             } catch (Throwable t) {
    669                 return new DataTables() {
    670                     @Override
    671                     public DataTable get(ULocale locale, boolean nullIfNotFound) {
    672                         return new DataTable(nullIfNotFound);
    673                     }
    674                 };
    675             }
    676         }
    677     }
    678 
    679     static abstract class ICUDataTables extends DataTables {
    680         private final String path;
    681 
    682         protected ICUDataTables(String path) {
    683             this.path = path;
    684         }
    685 
    686         @Override
    687         public DataTable get(ULocale locale, boolean nullIfNotFound) {
    688             return new ICUDataTable(path, locale, nullIfNotFound);
    689         }
    690     }
    691 
    692     static class LangDataTables {
    693         static final DataTables impl = DataTables.load("com.ibm.icu.impl.ICULangDataTables");
    694     }
    695 
    696     static class RegionDataTables {
    697         static final DataTables impl = DataTables.load("com.ibm.icu.impl.ICURegionDataTables");
    698     }
    699 
    700     public static enum DataTableType {
    701         LANG, REGION;
    702     }
    703 
    704     public static boolean haveData(DataTableType type) {
    705         switch (type) {
    706         case LANG: return LangDataTables.impl instanceof ICUDataTables;
    707         case REGION: return RegionDataTables.impl instanceof ICUDataTables;
    708         default:
    709             throw new IllegalArgumentException("unknown type: " + type);
    710         }
    711     }
    712 
    713     private StringBuilder appendWithSep(String s, StringBuilder b) {
    714         if (b.length() == 0) {
    715             b.append(s);
    716         } else {
    717             SimpleFormatterImpl.formatAndReplace(separatorFormat, b, null, b, s);
    718         }
    719         return b;
    720     }
    721 
    722     private static class Cache {
    723         private ULocale locale;
    724         private DialectHandling dialectHandling;
    725         private DisplayContext capitalization;
    726         private DisplayContext nameLength;
    727         private DisplayContext substituteHandling;
    728         private LocaleDisplayNames cache;
    729         public LocaleDisplayNames get(ULocale locale, DialectHandling dialectHandling) {
    730             if (!(dialectHandling == this.dialectHandling && DisplayContext.CAPITALIZATION_NONE == this.capitalization &&
    731                     DisplayContext.LENGTH_FULL == this.nameLength && DisplayContext.SUBSTITUTE == this.substituteHandling &&
    732                     locale.equals(this.locale))) {
    733                 this.locale = locale;
    734                 this.dialectHandling = dialectHandling;
    735                 this.capitalization = DisplayContext.CAPITALIZATION_NONE;
    736                 this.nameLength = DisplayContext.LENGTH_FULL;
    737                 this.substituteHandling = DisplayContext.SUBSTITUTE;
    738                 this.cache = new LocaleDisplayNamesImpl(locale, dialectHandling);
    739             }
    740             return cache;
    741         }
    742         public LocaleDisplayNames get(ULocale locale, DisplayContext... contexts) {
    743             DialectHandling dialectHandlingIn = DialectHandling.STANDARD_NAMES;
    744             DisplayContext capitalizationIn = DisplayContext.CAPITALIZATION_NONE;
    745             DisplayContext nameLengthIn = DisplayContext.LENGTH_FULL;
    746             DisplayContext substituteHandling = DisplayContext.SUBSTITUTE;
    747             for (DisplayContext contextItem : contexts) {
    748                 switch (contextItem.type()) {
    749                 case DIALECT_HANDLING:
    750                     dialectHandlingIn = (contextItem.value()==DisplayContext.STANDARD_NAMES.value())?
    751                             DialectHandling.STANDARD_NAMES: DialectHandling.DIALECT_NAMES;
    752                     break;
    753                 case CAPITALIZATION:
    754                     capitalizationIn = contextItem;
    755                     break;
    756                 case DISPLAY_LENGTH:
    757                     nameLengthIn = contextItem;
    758                     break;
    759                 case SUBSTITUTE_HANDLING:
    760                     substituteHandling = contextItem;
    761                     break;
    762                 default:
    763                     break;
    764                 }
    765             }
    766             if (!(dialectHandlingIn == this.dialectHandling && capitalizationIn == this.capitalization &&
    767                     nameLengthIn == this.nameLength && substituteHandling == this.substituteHandling &&
    768                     locale.equals(this.locale))) {
    769                 this.locale = locale;
    770                 this.dialectHandling = dialectHandlingIn;
    771                 this.capitalization = capitalizationIn;
    772                 this.nameLength = nameLengthIn;
    773                 this.substituteHandling = substituteHandling;
    774                 this.cache = new LocaleDisplayNamesImpl(locale, contexts);
    775             }
    776             return cache;
    777         }
    778     }
    779 }
    780