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