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