1 // Copyright (C) 2008-2012 IBM Corporation and Others. All Rights Reserved. 2 3 package org.unicode.cldr.util; 4 5 import java.util.Hashtable; 6 import java.util.Iterator; 7 import java.util.Set; 8 import java.util.TreeSet; 9 import java.util.concurrent.Callable; 10 import java.util.concurrent.ExecutionException; 11 12 import com.google.common.cache.Cache; 13 import com.google.common.cache.CacheBuilder; 14 import com.ibm.icu.text.LocaleDisplayNames; 15 import com.ibm.icu.text.Transform; 16 import com.ibm.icu.util.ULocale; 17 18 /** 19 * This class implements a CLDR UTS#35 compliant locale. 20 * It differs from ICU and Java locales in that it is singleton based, and that it is Comparable. 21 * It uses LocaleIDParser to do the heavy lifting of parsing. 22 * 23 * @author srl 24 * @see LocaleIDParser 25 * @see ULocale 26 */ 27 public final class CLDRLocale implements Comparable<CLDRLocale> { 28 private static final boolean DEBUG = false; 29 30 public interface NameFormatter { getDisplayName(CLDRLocale cldrLocale)31 String getDisplayName(CLDRLocale cldrLocale); 32 getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker)33 String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker); 34 getDisplayLanguage(CLDRLocale cldrLocale)35 String getDisplayLanguage(CLDRLocale cldrLocale); 36 getDisplayScript(CLDRLocale cldrLocale)37 String getDisplayScript(CLDRLocale cldrLocale); 38 getDisplayVariant(CLDRLocale cldrLocale)39 String getDisplayVariant(CLDRLocale cldrLocale); 40 getDisplayCountry(CLDRLocale cldrLocale)41 String getDisplayCountry(CLDRLocale cldrLocale); 42 } 43 44 public static class SimpleFormatter implements NameFormatter { 45 private LocaleDisplayNames ldn; 46 SimpleFormatter(ULocale displayLocale)47 public SimpleFormatter(ULocale displayLocale) { 48 this.ldn = LocaleDisplayNames.getInstance(displayLocale); 49 } 50 getDisplayNames()51 public LocaleDisplayNames getDisplayNames() { 52 return ldn; 53 } 54 setDisplayNames(LocaleDisplayNames ldn)55 public LocaleDisplayNames setDisplayNames(LocaleDisplayNames ldn) { 56 return this.ldn = ldn; 57 } 58 59 @Override getDisplayVariant(CLDRLocale cldrLocale)60 public String getDisplayVariant(CLDRLocale cldrLocale) { 61 return ldn.variantDisplayName(cldrLocale.getVariant()); 62 } 63 64 @Override getDisplayCountry(CLDRLocale cldrLocale)65 public String getDisplayCountry(CLDRLocale cldrLocale) { 66 return ldn.regionDisplayName(cldrLocale.getCountry()); 67 } 68 69 @Override getDisplayName(CLDRLocale cldrLocale)70 public String getDisplayName(CLDRLocale cldrLocale) { 71 StringBuffer sb = new StringBuffer(); 72 String l = cldrLocale.getLanguage(); 73 String s = cldrLocale.getScript(); 74 String r = cldrLocale.getCountry(); 75 String v = cldrLocale.getVariant(); 76 77 if (l != null && !l.isEmpty()) { 78 sb.append(getDisplayLanguage(cldrLocale)); 79 } else { 80 sb.append("?"); 81 } 82 if ((s != null && !s.isEmpty()) || 83 (r != null && !r.isEmpty()) || 84 (v != null && !v.isEmpty())) { 85 sb.append(" ("); 86 if (s != null && !s.isEmpty()) { 87 sb.append(getDisplayScript(cldrLocale)).append(","); 88 } 89 if (r != null && !r.isEmpty()) { 90 sb.append(getDisplayCountry(cldrLocale)).append(","); 91 } 92 if (v != null && !v.isEmpty()) { 93 sb.append(getDisplayVariant(cldrLocale)).append(","); 94 } 95 sb.replace(sb.length() - 1, sb.length(), ")"); 96 } 97 return sb.toString(); 98 } 99 100 @Override getDisplayScript(CLDRLocale cldrLocale)101 public String getDisplayScript(CLDRLocale cldrLocale) { 102 return ldn.scriptDisplayName(cldrLocale.getScript()); 103 } 104 105 @Override getDisplayLanguage(CLDRLocale cldrLocale)106 public String getDisplayLanguage(CLDRLocale cldrLocale) { 107 return ldn.languageDisplayName(cldrLocale.getLanguage()); 108 } 109 110 @Override getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker)111 public String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker) { 112 return getDisplayName(cldrLocale); 113 } 114 } 115 116 /** 117 * @author srl 118 * 119 * This formatter will delegate to CLDRFile.getName if a CLDRFile is given, otherwise StandardCodes 120 */ 121 public static class CLDRFormatter extends SimpleFormatter { 122 private FormatBehavior behavior = FormatBehavior.extend; 123 124 private CLDRFile file = null; 125 CLDRFormatter(CLDRFile fromFile)126 public CLDRFormatter(CLDRFile fromFile) { 127 super(CLDRLocale.getInstance(fromFile.getLocaleID()).toULocale()); 128 file = fromFile; 129 } 130 CLDRFormatter(CLDRFile fromFile, FormatBehavior behavior)131 public CLDRFormatter(CLDRFile fromFile, FormatBehavior behavior) { 132 super(CLDRLocale.getInstance(fromFile.getLocaleID()).toULocale()); 133 this.behavior = behavior; 134 file = fromFile; 135 } 136 CLDRFormatter()137 public CLDRFormatter() { 138 super(ULocale.ROOT); 139 } 140 CLDRFormatter(FormatBehavior behavior)141 public CLDRFormatter(FormatBehavior behavior) { 142 super(ULocale.ROOT); 143 this.behavior = behavior; 144 } 145 146 @Override getDisplayVariant(CLDRLocale cldrLocale)147 public String getDisplayVariant(CLDRLocale cldrLocale) { 148 if (file != null) return file.getName("variant", cldrLocale.getVariant()); 149 return tryForBetter(super.getDisplayVariant(cldrLocale), 150 cldrLocale.getVariant(), 151 "variant"); 152 } 153 154 @Override getDisplayName(CLDRLocale cldrLocale)155 public String getDisplayName(CLDRLocale cldrLocale) { 156 if (file != null) return file.getName(cldrLocale.toDisplayLanguageTag(), true, null); 157 return super.getDisplayName(cldrLocale); 158 } 159 160 @Override getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker)161 public String getDisplayName(CLDRLocale cldrLocale, boolean onlyConstructCompound, Transform<String, String> altPicker) { 162 if (file != null) return file.getName(cldrLocale.toDisplayLanguageTag(), onlyConstructCompound, altPicker); 163 return super.getDisplayName(cldrLocale); 164 } 165 166 @Override getDisplayScript(CLDRLocale cldrLocale)167 public String getDisplayScript(CLDRLocale cldrLocale) { 168 if (file != null) return file.getName("script", cldrLocale.getScript()); 169 return tryForBetter(super.getDisplayScript(cldrLocale), 170 cldrLocale.getScript(), 171 "language"); 172 } 173 174 @Override getDisplayLanguage(CLDRLocale cldrLocale)175 public String getDisplayLanguage(CLDRLocale cldrLocale) { 176 if (file != null) return file.getName("language", cldrLocale.getLanguage()); 177 return tryForBetter(super.getDisplayLanguage(cldrLocale), 178 cldrLocale.getLanguage(), 179 "language"); 180 } 181 182 @Override getDisplayCountry(CLDRLocale cldrLocale)183 public String getDisplayCountry(CLDRLocale cldrLocale) { 184 if (file != null) return file.getName("territory", cldrLocale.getCountry()); 185 return tryForBetter(super.getDisplayLanguage(cldrLocale), 186 cldrLocale.getLanguage(), 187 "territory"); 188 } 189 tryForBetter(String superString, String code, String type)190 private String tryForBetter(String superString, String code, String type) { 191 if (superString.equals(code)) { 192 String fromLst = StandardCodes.make().getData("language", code); 193 if (fromLst != null && !fromLst.equals(code)) { 194 switch (behavior) { 195 case replace: 196 return fromLst; 197 case extend: 198 return superString + " [" + fromLst + "]"; 199 case extendHtml: 200 return superString + " [<i>" + fromLst + "</i>]"; 201 } 202 } 203 } 204 return superString; 205 } 206 } 207 208 public enum FormatBehavior { 209 replace, extend, extendHtml 210 }; 211 212 /** 213 * Reference to the parent CLDRLocale 214 */ 215 private CLDRLocale parent = null; 216 /** 217 * Cached ICU format locale 218 */ 219 private ULocale ulocale; 220 /** 221 * base name, 'without parameters'. Currently same as fullname. 222 */ 223 private String basename; 224 /** 225 * Full name 226 */ 227 private String fullname; 228 /** 229 * The LocaleIDParser interprets the various parts (language, country, script, etc). 230 */ 231 private LocaleIDParser parts = null; 232 233 /** 234 * Construct a CLDRLocale from an ICU ULocale. 235 * Internal, called by the factory function. 236 */ CLDRLocale(ULocale loc)237 private CLDRLocale(ULocale loc) { 238 init(loc); 239 } 240 241 /** 242 * Returns the BCP47 langauge tag for all except root. For root, returns "root". 243 * @return 244 */ toDisplayLanguageTag()245 private String toDisplayLanguageTag() { 246 if (getBaseName().equals("root")) { 247 return "root"; 248 } else { 249 return toLanguageTag(); 250 } 251 } 252 253 /** 254 * Return BCP47 language tag 255 * @return 256 */ toLanguageTag()257 public String toLanguageTag() { 258 return ulocale.toLanguageTag(); 259 } 260 261 /** 262 * Construct a CLDRLocale from a string with the full locale ID. 263 * Internal, called by the factory function. 264 * 265 * @param str 266 */ CLDRLocale(String str)267 private CLDRLocale(String str) { 268 init(str); 269 } 270 271 /** 272 * Initialize a CLDRLocale from a ULocale 273 * 274 * @param loc 275 */ init(ULocale loc)276 private void init(ULocale loc) { 277 ulocale = loc; 278 init(loc.getBaseName()); 279 } 280 281 /** 282 * Initialize a CLDRLocale from a string. 283 * 284 * @param str 285 */ init(String str)286 private void init(String str) { 287 // if(str.length()==0) { 288 // str = "root"; 289 // } 290 str = process(str); 291 // System.err.println("bn: " + str); 292 if (str.equals(ULocale.ROOT.getBaseName()) || str.equalsIgnoreCase("root")) { 293 fullname = "root"; 294 parent = null; 295 } else { 296 parts = new LocaleIDParser(); 297 parts.set(str); 298 fullname = parts.toString(); 299 String parentId = LocaleIDParser.getParent(str); 300 if (DEBUG) System.out.println(str + " par = " + parentId); 301 if (parentId != null) { 302 parent = CLDRLocale.getInstance(parentId); 303 } else { 304 parent = null; // probably, we are root or we are supplemental 305 } 306 } 307 basename = fullname; 308 if (ulocale == null) { 309 ulocale = new ULocale(fullname); 310 } 311 } 312 313 /** 314 * Return the full locale name, in CLDR format. 315 */ toString()316 public String toString() { 317 return fullname; 318 } 319 320 /** 321 * Return the base locale name, in CLDR format, without any @keywords 322 * 323 * @return 324 */ getBaseName()325 public String getBaseName() { 326 return basename; 327 } 328 329 /** 330 * internal: process a string from ICU to CLDR form. For now, just collapse double underscores. 331 * 332 * @param baseName 333 * @return 334 * @internal 335 */ process(String baseName)336 private String process(String baseName) { 337 return baseName.replaceAll("__", "_"); 338 } 339 340 /** 341 * Compare to another CLDRLocale. Uses string order of toString(). 342 */ compareTo(CLDRLocale o)343 public int compareTo(CLDRLocale o) { 344 if (o == this) return 0; 345 return fullname.compareTo(o.fullname); 346 } 347 348 /** 349 * Hashcode - is the hashcode of the full string 350 */ hashCode()351 public int hashCode() { 352 return fullname.hashCode(); 353 } 354 355 /** 356 * Convert to an ICU compatible ULocale. 357 * 358 * @return 359 */ toULocale()360 public ULocale toULocale() { 361 return ulocale; 362 } 363 364 /** 365 * Allocate a CLDRLocale (could be a singleton). If null is passed in, null will be returned. 366 * 367 * @param s 368 * @return 369 */ getInstance(String s)370 public static CLDRLocale getInstance(String s) { 371 if (s == null) return null; 372 CLDRLocale loc = stringToLoc.get(s); 373 if (loc == null) { 374 loc = new CLDRLocale(s); 375 loc.register(); 376 } 377 return loc; 378 } 379 380 /** 381 * Public factory function. Allocate a CLDRLocale (could be a singleton). If null is passed in, null will be 382 * returned. 383 * 384 * @param u 385 * @return 386 */ getInstance(ULocale u)387 public static CLDRLocale getInstance(ULocale u) { 388 if (u == null) return null; 389 CLDRLocale loc = ulocToLoc.get(u); 390 if (loc == null) { 391 loc = new CLDRLocale(u); 392 loc.register(); 393 } 394 return loc; 395 } 396 397 /** 398 * Register the singleton instance. 399 */ register()400 private void register() { 401 stringToLoc.put(this.toString(), this); 402 ulocToLoc.put(this.toULocale(), this); 403 } 404 405 private static Hashtable<String, CLDRLocale> stringToLoc = new Hashtable<String, CLDRLocale>(); 406 private static Hashtable<ULocale, CLDRLocale> ulocToLoc = new Hashtable<ULocale, CLDRLocale>(); 407 408 /** 409 * Return the parent locale of this item. Null if no parent (root has no parent) 410 * 411 * @return 412 */ getParent()413 public CLDRLocale getParent() { 414 return parent; 415 } 416 417 /** 418 * Returns true if other is equal to or is an ancestor of this, false otherwise 419 */ childOf(CLDRLocale other)420 public boolean childOf(CLDRLocale other) { 421 if (other == null) return false; 422 if (other == this) return true; 423 if (parent == null) return false; // end 424 return parent.childOf(other); 425 } 426 427 /** 428 * Return an iterator that will iterate over locale, parent, parent etc, finally reaching root. 429 * 430 * @return 431 */ getParentIterator()432 public Iterable<CLDRLocale> getParentIterator() { 433 final CLDRLocale newThis = this; 434 return new Iterable<CLDRLocale>() { 435 public Iterator<CLDRLocale> iterator() { 436 return new Iterator<CLDRLocale>() { 437 CLDRLocale what = newThis; 438 439 public boolean hasNext() { 440 // TODO Auto-generated method stub 441 return what.getParent() != null; 442 } 443 444 public CLDRLocale next() { 445 // TODO Auto-generated method stub 446 CLDRLocale curr = what; 447 if (what != null) { 448 what = what.getParent(); 449 } 450 return curr; 451 } 452 453 public void remove() { 454 throw new InternalError("unmodifiable iterator"); 455 } 456 457 }; 458 } 459 }; 460 } 461 462 /** 463 * Get the 'language' locale, as an object. Might be 'this'. 464 * @return 465 */ 466 public CLDRLocale getLanguageLocale() { 467 return getInstance(getLanguage()); 468 } 469 470 public String getLanguage() { 471 return parts == null ? fullname : parts.getLanguage(); 472 } 473 474 public String getScript() { 475 return parts == null ? null : parts.getScript(); 476 } 477 478 public boolean isLanguageLocale() { 479 return this.equals(getLanguageLocale()); 480 } 481 482 /** 483 * Return the region 484 * 485 * @return 486 */ 487 public String getCountry() { 488 return parts == null ? null : parts.getRegion(); 489 } 490 491 /** 492 * Return "the" variant. 493 * 494 * @return 495 */ 496 public String getVariant() { 497 return toULocale().getVariant(); // TODO: replace with parts? 498 } 499 500 /** 501 * Most objects should be singletons, and so equality/inequality comparison is done first. 502 */ 503 public boolean equals(Object o) { 504 if (o == this) return true; 505 if (!(o instanceof CLDRLocale)) return false; 506 return (0 == compareTo((CLDRLocale) o)); 507 } 508 509 /** 510 * The root locale, a singleton. 511 */ 512 public static final CLDRLocale ROOT = getInstance(ULocale.ROOT); 513 514 public String getDisplayName() { 515 return getDisplayName(getDefaultFormatter()); 516 } 517 518 public String getDisplayRegion() { 519 return getDisplayCountry(getDefaultFormatter()); 520 } 521 522 public String getDisplayVariant() { 523 return getDisplayVariant(getDefaultFormatter()); 524 } 525 526 public String getDisplayName(boolean combined, Transform<String, String> picker) { 527 return getDisplayName(getDefaultFormatter(), combined, picker); 528 } 529 530 /** 531 * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to 532 * ULocale.getDisplay___(displayLocale) 533 * 534 * @param displayLocale 535 * @return 536 */ 537 public String getDisplayName(NameFormatter displayLocale) { 538 if (displayLocale == null) displayLocale = getDefaultFormatter(); 539 return displayLocale.getDisplayName(this); 540 } 541 542 // private static LruMap<ULocale, NameFormatter> defaultFormatters = new LruMap<ULocale, NameFormatter>(1); 543 private static Cache<ULocale, NameFormatter> defaultFormatters = CacheBuilder.newBuilder().initialCapacity(1).build(); 544 private static NameFormatter gDefaultFormatter = getSimpleFormatterFor(ULocale.getDefault()); 545 546 public static NameFormatter getSimpleFormatterFor(ULocale loc) { 547 // NameFormatter nf = defaultFormatters.get(loc); 548 // if (nf == null) { 549 // nf = new SimpleFormatter(loc); 550 // defaultFormatters.put(loc, nf); 551 // } 552 // return nf; 553 // return defaultFormatters.getIfPresent(loc); 554 final ULocale uLocFinal = loc; 555 try { 556 return defaultFormatters.get(loc, new Callable<NameFormatter>() { 557 558 @Override 559 public NameFormatter call() throws Exception { 560 return new SimpleFormatter(uLocFinal); 561 } 562 }); 563 } catch (ExecutionException e) { 564 // TODO Auto-generated catch block 565 e.printStackTrace(); 566 return null; 567 } 568 } 569 570 public String getDisplayName(ULocale displayLocale) { 571 return getSimpleFormatterFor(displayLocale).getDisplayName(this); 572 } 573 574 public static NameFormatter getDefaultFormatter() { 575 return gDefaultFormatter; 576 } 577 578 public static NameFormatter setDefaultFormatter(NameFormatter nf) { 579 return gDefaultFormatter = nf; 580 } 581 582 /** 583 * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to 584 * ULocale.getDisplay___(displayLocale) 585 * 586 * @param displayLocale 587 * @return 588 */ 589 public String getDisplayCountry(NameFormatter displayLocale) { 590 if (displayLocale == null) displayLocale = getDefaultFormatter(); 591 return displayLocale.getDisplayCountry(this); 592 } 593 594 /** 595 * These functions wrap calls to the displayLocale, but are provided to supply an interface that looks similar to 596 * ULocale.getDisplay___(displayLocale) 597 * 598 * @param displayLocale 599 * @return 600 */ 601 public String getDisplayVariant(NameFormatter displayLocale) { 602 if (displayLocale == null) displayLocale = getDefaultFormatter(); 603 return displayLocale.getDisplayVariant(this); 604 } 605 606 /** 607 * Construct an instance from an array 608 * 609 * @param available 610 * @return 611 */ 612 public static Set<CLDRLocale> getInstance(Iterable<String> available) { 613 Set<CLDRLocale> s = new TreeSet<CLDRLocale>(); 614 for (String str : available) { 615 s.add(CLDRLocale.getInstance(str)); 616 } 617 return s; 618 } 619 620 public interface SublocaleProvider { 621 public Set<CLDRLocale> subLocalesOf(CLDRLocale forLocale); 622 } 623 624 public String getDisplayName(NameFormatter engFormat, boolean combined, Transform<String, String> picker) { 625 return engFormat.getDisplayName(this, combined, picker); 626 } 627 628 /** 629 * Return the highest parent that is a child of root, or null. 630 * @return highest parent, or null. ROOT.getHighestNonrootParent() also returns null. 631 */ 632 public CLDRLocale getHighestNonrootParent() { 633 CLDRLocale res; 634 if (this == ROOT) { 635 res = null; 636 ; 637 } else if (this.parent == ROOT) { 638 res = this; 639 } else if (this.parent == null) { 640 res = this; 641 } else { 642 res = parent.getHighestNonrootParent(); 643 } 644 if (DEBUG) System.out.println(this + ".HNRP=" + res); 645 return res; 646 } 647 } 648