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 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 // Our data uses 'root' to access display names for the root locale in the 309 // "Languages" table. 310 if (locale.getBaseName().length() == 0) { 311 lang = "root"; 312 } 313 String script = locale.getScript(); 314 String country = locale.getCountry(); 315 String variant = locale.getVariant(); 316 317 boolean hasScript = script.length() > 0; 318 boolean hasCountry = country.length() > 0; 319 boolean hasVariant = variant.length() > 0; 320 321 // always have a value for lang 322 if (dialectHandling == DialectHandling.DIALECT_NAMES) { 323 do { // loop construct is so we can break early out of search 324 if (hasScript && hasCountry) { 325 String langScriptCountry = lang + '_' + script + '_' + country; 326 String result = localeIdName(langScriptCountry); 327 if (result != null && !result.equals(langScriptCountry)) { 328 resultName = result; 329 hasScript = false; 330 hasCountry = false; 331 break; 332 } 333 } 334 if (hasScript) { 335 String langScript = lang + '_' + script; 336 String result = localeIdName(langScript); 337 if (result != null && !result.equals(langScript)) { 338 resultName = result; 339 hasScript = false; 340 break; 341 } 342 } 343 if (hasCountry) { 344 String langCountry = lang + '_' + country; 345 String result = localeIdName(langCountry); 346 if (result != null && !result.equals(langCountry)) { 347 resultName = result; 348 hasCountry = false; 349 break; 350 } 351 } 352 } while (false); 353 } 354 355 if (resultName == null) { 356 String result = localeIdName(lang); 357 if (result == null) { return null; } 358 resultName = result 359 .replace(formatOpenParen, formatReplaceOpenParen) 360 .replace(formatCloseParen, formatReplaceCloseParen); 361 } 362 363 StringBuilder buf = new StringBuilder(); 364 if (hasScript) { 365 // first element, don't need appendWithSep 366 String result = scriptDisplayNameInContext(script, true); 367 if (result == null) { return null; } 368 buf.append(result 369 .replace(formatOpenParen, formatReplaceOpenParen) 370 .replace(formatCloseParen, formatReplaceCloseParen)); 371 } 372 if (hasCountry) { 373 String result = regionDisplayName(country, true); 374 if (result == null) { return null; } 375 appendWithSep(result 376 .replace(formatOpenParen, formatReplaceOpenParen) 377 .replace(formatCloseParen, formatReplaceCloseParen), buf); 378 } 379 if (hasVariant) { 380 String result = variantDisplayName(variant, true); 381 if (result == null) { return null; } 382 appendWithSep(result 383 .replace(formatOpenParen, formatReplaceOpenParen) 384 .replace(formatCloseParen, formatReplaceCloseParen), buf); 385 } 386 387 Iterator<String> keys = locale.getKeywords(); 388 if (keys != null) { 389 while (keys.hasNext()) { 390 String key = keys.next(); 391 String value = locale.getKeywordValue(key); 392 String keyDisplayName = keyDisplayName(key, true); 393 if (keyDisplayName == null) { return null; } 394 keyDisplayName = keyDisplayName 395 .replace(formatOpenParen, formatReplaceOpenParen) 396 .replace(formatCloseParen, formatReplaceCloseParen); 397 String valueDisplayName = keyValueDisplayName(key, value, true); 398 if (valueDisplayName == null) { return null; } 399 valueDisplayName = valueDisplayName 400 .replace(formatOpenParen, formatReplaceOpenParen) 401 .replace(formatCloseParen, formatReplaceCloseParen); 402 if (!valueDisplayName.equals(value)) { 403 appendWithSep(valueDisplayName, buf); 404 } else if (!key.equals(keyDisplayName)) { 405 String keyValue = SimpleFormatterImpl.formatCompiledPattern( 406 keyTypeFormat, keyDisplayName, valueDisplayName); 407 appendWithSep(keyValue, buf); 408 } else { 409 appendWithSep(keyDisplayName, buf) 410 .append("=") 411 .append(valueDisplayName); 412 } 413 } 414 } 415 416 String resultRemainder = null; 417 if (buf.length() > 0) { 418 resultRemainder = buf.toString(); 419 } 420 421 if (resultRemainder != null) { 422 resultName = SimpleFormatterImpl.formatCompiledPattern( 423 format, resultName, resultRemainder); 424 } 425 426 return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, resultName); 427 } 428 localeIdName(String localeId)429 private String localeIdName(String localeId) { 430 if (nameLength == DisplayContext.LENGTH_SHORT) { 431 String locIdName = langData.get("Languages%short", localeId); 432 if (locIdName != null && !locIdName.equals(localeId)) { 433 return locIdName; 434 } 435 } 436 return langData.get("Languages", localeId); 437 } 438 439 @Override languageDisplayName(String lang)440 public String languageDisplayName(String lang) { 441 // Special case to eliminate non-languages, which pollute our data. 442 if (lang.equals("root") || lang.indexOf('_') != -1) { 443 return substituteHandling == DisplayContext.SUBSTITUTE ? lang : null; 444 } 445 if (nameLength == DisplayContext.LENGTH_SHORT) { 446 String langName = langData.get("Languages%short", lang); 447 if (langName != null && !langName.equals(lang)) { 448 return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langName); 449 } 450 } 451 return adjustForUsageAndContext(CapitalizationContextUsage.LANGUAGE, langData.get("Languages", lang)); 452 } 453 454 @Override scriptDisplayName(String script)455 public String scriptDisplayName(String script) { 456 String str = langData.get("Scripts%stand-alone", script); 457 if (str == null || str.equals(script)) { 458 if (nameLength == DisplayContext.LENGTH_SHORT) { 459 str = langData.get("Scripts%short", script); 460 if (str != null && !str.equals(script)) { 461 return adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, str); 462 } 463 } 464 str = langData.get("Scripts", script); 465 } 466 return adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, str); 467 } 468 scriptDisplayNameInContext(String script, boolean skipAdjust)469 private String scriptDisplayNameInContext(String script, boolean skipAdjust) { 470 if (nameLength == DisplayContext.LENGTH_SHORT) { 471 String scriptName = langData.get("Scripts%short", script); 472 if (scriptName != null && !scriptName.equals(script)) { 473 return skipAdjust? scriptName: adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, scriptName); 474 } 475 } 476 String scriptName = langData.get("Scripts", script); 477 return skipAdjust? scriptName: adjustForUsageAndContext(CapitalizationContextUsage.SCRIPT, scriptName); 478 } 479 480 @Override scriptDisplayNameInContext(String script)481 public String scriptDisplayNameInContext(String script) { 482 return scriptDisplayNameInContext(script, false); 483 } 484 485 @Override scriptDisplayName(int scriptCode)486 public String scriptDisplayName(int scriptCode) { 487 return scriptDisplayName(UScript.getShortName(scriptCode)); 488 } 489 regionDisplayName(String region, boolean skipAdjust)490 private String regionDisplayName(String region, boolean skipAdjust) { 491 if (nameLength == DisplayContext.LENGTH_SHORT) { 492 String regionName = regionData.get("Countries%short", region); 493 if (regionName != null && !regionName.equals(region)) { 494 return skipAdjust? regionName: adjustForUsageAndContext(CapitalizationContextUsage.TERRITORY, regionName); 495 } 496 } 497 String regionName = regionData.get("Countries", region); 498 return skipAdjust? regionName: adjustForUsageAndContext(CapitalizationContextUsage.TERRITORY, regionName); 499 } 500 501 @Override regionDisplayName(String region)502 public String regionDisplayName(String region) { 503 return regionDisplayName(region, false); 504 } 505 variantDisplayName(String variant, boolean skipAdjust)506 private String variantDisplayName(String variant, boolean skipAdjust) { 507 // don't have a resource for short variant names 508 String variantName = langData.get("Variants", variant); 509 return skipAdjust? variantName: adjustForUsageAndContext(CapitalizationContextUsage.VARIANT, variantName); 510 } 511 512 @Override variantDisplayName(String variant)513 public String variantDisplayName(String variant) { 514 return variantDisplayName(variant, false); 515 } 516 keyDisplayName(String key, boolean skipAdjust)517 private String keyDisplayName(String key, boolean skipAdjust) { 518 // don't have a resource for short key names 519 String keyName = langData.get("Keys", key); 520 return skipAdjust? keyName: adjustForUsageAndContext(CapitalizationContextUsage.KEY, keyName); 521 } 522 523 @Override keyDisplayName(String key)524 public String keyDisplayName(String key) { 525 return keyDisplayName(key, false); 526 } 527 keyValueDisplayName(String key, String value, boolean skipAdjust)528 private String keyValueDisplayName(String key, String value, boolean skipAdjust) { 529 String keyValueName = null; 530 531 if (key.equals("currency")) { 532 keyValueName = currencyDisplayInfo.getName(AsciiUtil.toUpperString(value)); 533 if (keyValueName == null) { 534 keyValueName = value; 535 } 536 } else { 537 if (nameLength == DisplayContext.LENGTH_SHORT) { 538 String tmp = langData.get("Types%short", key, value); 539 if (tmp != null && !tmp.equals(value)) { 540 keyValueName = tmp; 541 } 542 } 543 if (keyValueName == null) { 544 keyValueName = langData.get("Types", key, value); 545 } 546 } 547 548 return skipAdjust? keyValueName: adjustForUsageAndContext(CapitalizationContextUsage.KEYVALUE, keyValueName); 549 } 550 551 @Override keyValueDisplayName(String key, String value)552 public String keyValueDisplayName(String key, String value) { 553 return keyValueDisplayName(key, value, false); 554 } 555 556 @Override getUiListCompareWholeItems(Set<ULocale> localeSet, Comparator<UiListItem> comparator)557 public List<UiListItem> getUiListCompareWholeItems(Set<ULocale> localeSet, Comparator<UiListItem> comparator) { 558 DisplayContext capContext = getContext(Type.CAPITALIZATION); 559 560 List<UiListItem> result = new ArrayList<UiListItem>(); 561 Map<ULocale,Set<ULocale>> baseToLocales = new HashMap<ULocale,Set<ULocale>>(); 562 ULocale.Builder builder = new ULocale.Builder(); 563 for (ULocale locOriginal : localeSet) { 564 builder.setLocale(locOriginal); // verify well-formed. We do this here so that we consistently throw exception 565 ULocale loc = ULocale.addLikelySubtags(locOriginal); 566 ULocale base = new ULocale(loc.getLanguage()); 567 Set<ULocale> locales = baseToLocales.get(base); 568 if (locales == null) { 569 baseToLocales.put(base, locales = new HashSet<ULocale>()); 570 } 571 locales.add(loc); 572 } 573 for (Entry<ULocale, Set<ULocale>> entry : baseToLocales.entrySet()) { 574 ULocale base = entry.getKey(); 575 Set<ULocale> values = entry.getValue(); 576 if (values.size() == 1) { 577 ULocale locale = values.iterator().next(); 578 result.add(newRow(ULocale.minimizeSubtags(locale, ULocale.Minimize.FAVOR_SCRIPT), capContext)); 579 } else { 580 Set<String> scripts = new HashSet<String>(); 581 Set<String> regions = new HashSet<String>(); 582 // need the follow two steps to make sure that unusual scripts or regions are displayed 583 ULocale maxBase = ULocale.addLikelySubtags(base); 584 scripts.add(maxBase.getScript()); 585 regions.add(maxBase.getCountry()); 586 for (ULocale locale : values) { 587 scripts.add(locale.getScript()); 588 regions.add(locale.getCountry()); 589 } 590 boolean hasScripts = scripts.size() > 1; 591 boolean hasRegions = regions.size() > 1; 592 for (ULocale locale : values) { 593 ULocale.Builder modified = builder.setLocale(locale); 594 if (!hasScripts) { 595 modified.setScript(""); 596 } 597 if (!hasRegions) { 598 modified.setRegion(""); 599 } 600 result.add(newRow(modified.build(), capContext)); 601 } 602 } 603 } 604 Collections.sort(result, comparator); 605 return result; 606 } 607 newRow(ULocale modified, DisplayContext capContext)608 private UiListItem newRow(ULocale modified, DisplayContext capContext) { 609 ULocale minimized = ULocale.minimizeSubtags(modified, ULocale.Minimize.FAVOR_SCRIPT); 610 String tempName = modified.getDisplayName(locale); 611 boolean titlecase = capContext == DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU; 612 String nameInDisplayLocale = 613 titlecase ? toTitleWholeStringNoLowercase(locale, tempName) : tempName; 614 tempName = modified.getDisplayName(modified); 615 String nameInSelf = capContext == 616 DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU ? 617 toTitleWholeStringNoLowercase(modified, tempName) : tempName; 618 return new UiListItem(minimized, modified, nameInDisplayLocale, nameInSelf); 619 } 620 621 public static class DataTable { 622 final boolean nullIfNotFound; 623 DataTable(boolean nullIfNotFound)624 DataTable(boolean nullIfNotFound) { 625 this.nullIfNotFound = nullIfNotFound; 626 } 627 getLocale()628 ULocale getLocale() { 629 return ULocale.ROOT; 630 } 631 get(String tableName, String code)632 String get(String tableName, String code) { 633 return get(tableName, null, code); 634 } 635 get(String tableName, String subTableName, String code)636 String get(String tableName, String subTableName, String code) { 637 return nullIfNotFound ? null : code; 638 } 639 } 640 641 static class ICUDataTable extends DataTable { 642 private final ICUResourceBundle bundle; 643 ICUDataTable(String path, ULocale locale, boolean nullIfNotFound)644 public ICUDataTable(String path, ULocale locale, boolean nullIfNotFound) { 645 super(nullIfNotFound); 646 this.bundle = (ICUResourceBundle) UResourceBundle.getBundleInstance( 647 path, locale.getBaseName()); 648 } 649 650 @Override getLocale()651 public ULocale getLocale() { 652 return bundle.getULocale(); 653 } 654 655 @Override get(String tableName, String subTableName, String code)656 public String get(String tableName, String subTableName, String code) { 657 return ICUResourceTableAccess.getTableString(bundle, tableName, subTableName, 658 code, nullIfNotFound ? null : code); 659 } 660 } 661 662 static abstract class DataTables { get(ULocale locale, boolean nullIfNotFound)663 public abstract DataTable get(ULocale locale, boolean nullIfNotFound); load(String className)664 public static DataTables load(String className) { 665 try { 666 return (DataTables) Class.forName(className).newInstance(); 667 } catch (Throwable t) { 668 return new DataTables() { 669 @Override 670 public DataTable get(ULocale locale, boolean nullIfNotFound) { 671 return new DataTable(nullIfNotFound); 672 } 673 }; 674 } 675 } 676 } 677 678 static abstract class ICUDataTables extends DataTables { 679 private final String path; 680 ICUDataTables(String path)681 protected ICUDataTables(String path) { 682 this.path = path; 683 } 684 685 @Override get(ULocale locale, boolean nullIfNotFound)686 public DataTable get(ULocale locale, boolean nullIfNotFound) { 687 return new ICUDataTable(path, locale, nullIfNotFound); 688 } 689 } 690 691 static class LangDataTables { 692 static final DataTables impl = DataTables.load("com.ibm.icu.impl.ICULangDataTables"); 693 } 694 695 static class RegionDataTables { 696 static final DataTables impl = DataTables.load("com.ibm.icu.impl.ICURegionDataTables"); 697 } 698 699 public static enum DataTableType { 700 LANG, REGION; 701 } 702 haveData(DataTableType type)703 public static boolean haveData(DataTableType type) { 704 switch (type) { 705 case LANG: return LangDataTables.impl instanceof ICUDataTables; 706 case REGION: return RegionDataTables.impl instanceof ICUDataTables; 707 default: 708 throw new IllegalArgumentException("unknown type: " + type); 709 } 710 } 711 appendWithSep(String s, StringBuilder b)712 private StringBuilder appendWithSep(String s, StringBuilder b) { 713 if (b.length() == 0) { 714 b.append(s); 715 } else { 716 SimpleFormatterImpl.formatAndReplace(separatorFormat, b, null, b, s); 717 } 718 return b; 719 } 720 721 private static class Cache { 722 private ULocale locale; 723 private DialectHandling dialectHandling; 724 private DisplayContext capitalization; 725 private DisplayContext nameLength; 726 private DisplayContext substituteHandling; 727 private LocaleDisplayNames cache; get(ULocale locale, DialectHandling dialectHandling)728 public LocaleDisplayNames get(ULocale locale, DialectHandling dialectHandling) { 729 if (!(dialectHandling == this.dialectHandling && DisplayContext.CAPITALIZATION_NONE == this.capitalization && 730 DisplayContext.LENGTH_FULL == this.nameLength && DisplayContext.SUBSTITUTE == this.substituteHandling && 731 locale.equals(this.locale))) { 732 this.locale = locale; 733 this.dialectHandling = dialectHandling; 734 this.capitalization = DisplayContext.CAPITALIZATION_NONE; 735 this.nameLength = DisplayContext.LENGTH_FULL; 736 this.substituteHandling = DisplayContext.SUBSTITUTE; 737 this.cache = new LocaleDisplayNamesImpl(locale, dialectHandling); 738 } 739 return cache; 740 } get(ULocale locale, DisplayContext... contexts)741 public LocaleDisplayNames get(ULocale locale, DisplayContext... contexts) { 742 DialectHandling dialectHandlingIn = DialectHandling.STANDARD_NAMES; 743 DisplayContext capitalizationIn = DisplayContext.CAPITALIZATION_NONE; 744 DisplayContext nameLengthIn = DisplayContext.LENGTH_FULL; 745 DisplayContext substituteHandling = DisplayContext.SUBSTITUTE; 746 for (DisplayContext contextItem : contexts) { 747 switch (contextItem.type()) { 748 case DIALECT_HANDLING: 749 dialectHandlingIn = (contextItem.value()==DisplayContext.STANDARD_NAMES.value())? 750 DialectHandling.STANDARD_NAMES: DialectHandling.DIALECT_NAMES; 751 break; 752 case CAPITALIZATION: 753 capitalizationIn = contextItem; 754 break; 755 case DISPLAY_LENGTH: 756 nameLengthIn = contextItem; 757 break; 758 case SUBSTITUTE_HANDLING: 759 substituteHandling = contextItem; 760 break; 761 default: 762 break; 763 } 764 } 765 if (!(dialectHandlingIn == this.dialectHandling && capitalizationIn == this.capitalization && 766 nameLengthIn == this.nameLength && substituteHandling == this.substituteHandling && 767 locale.equals(this.locale))) { 768 this.locale = locale; 769 this.dialectHandling = dialectHandlingIn; 770 this.capitalization = capitalizationIn; 771 this.nameLength = nameLengthIn; 772 this.substituteHandling = substituteHandling; 773 this.cache = new LocaleDisplayNamesImpl(locale, contexts); 774 } 775 return cache; 776 } 777 } 778 } 779