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