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