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