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