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