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