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) 2001-2016, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.impl; 10 11 import java.util.Collections; 12 import java.util.Locale; 13 import java.util.Map; 14 import java.util.Set; 15 16 import com.ibm.icu.util.ULocale; 17 18 public class ICULocaleService extends ICUService { 19 private ULocale fallbackLocale; 20 private String fallbackLocaleName; 21 22 /** 23 * Construct an ICULocaleService. 24 */ ICULocaleService()25 public ICULocaleService() { 26 } 27 28 /** 29 * Construct an ICULocaleService with a name (useful for debugging). 30 */ ICULocaleService(String name)31 public ICULocaleService(String name) { 32 super(name); 33 } 34 35 /** 36 * Convenience override for callers using locales. This calls 37 * get(ULocale, int, ULocale[]) with KIND_ANY for kind and null for 38 * actualReturn. 39 */ get(ULocale locale)40 public Object get(ULocale locale) { 41 return get(locale, LocaleKey.KIND_ANY, null); 42 } 43 44 /** 45 * Convenience override for callers using locales. This calls 46 * get(ULocale, int, ULocale[]) with a null actualReturn. 47 */ get(ULocale locale, int kind)48 public Object get(ULocale locale, int kind) { 49 return get(locale, kind, null); 50 } 51 52 /** 53 * Convenience override for callers using locales. This calls 54 * get(ULocale, int, ULocale[]) with KIND_ANY for kind. 55 */ get(ULocale locale, ULocale[] actualReturn)56 public Object get(ULocale locale, ULocale[] actualReturn) { 57 return get(locale, LocaleKey.KIND_ANY, actualReturn); 58 } 59 60 /** 61 * Convenience override for callers using locales. This uses 62 * createKey(ULocale.toString(), kind) to create a key, calls getKey, and then 63 * if actualReturn is not null, returns the actualResult from 64 * getKey (stripping any prefix) into a ULocale. 65 */ get(ULocale locale, int kind, ULocale[] actualReturn)66 public Object get(ULocale locale, int kind, ULocale[] actualReturn) { 67 Key key = createKey(locale, kind); 68 if (actualReturn == null) { 69 return getKey(key); 70 } 71 72 String[] temp = new String[1]; 73 Object result = getKey(key, temp); 74 if (result != null) { 75 int n = temp[0].indexOf("/"); 76 if (n >= 0) { 77 temp[0] = temp[0].substring(n+1); 78 } 79 actualReturn[0] = new ULocale(temp[0]); 80 } 81 return result; 82 } 83 84 /** 85 * Convenience override for callers using locales. This calls 86 * registerObject(Object, ULocale, int kind, boolean visible) 87 * passing KIND_ANY for the kind, and true for the visibility. 88 */ registerObject(Object obj, ULocale locale)89 public Factory registerObject(Object obj, ULocale locale) { 90 return registerObject(obj, locale, LocaleKey.KIND_ANY, true); 91 } 92 93 /** 94 * Convenience override for callers using locales. This calls 95 * registerObject(Object, ULocale, int kind, boolean visible) 96 * passing KIND_ANY for the kind. 97 */ registerObject(Object obj, ULocale locale, boolean visible)98 public Factory registerObject(Object obj, ULocale locale, boolean visible) { 99 return registerObject(obj, locale, LocaleKey.KIND_ANY, visible); 100 } 101 102 /** 103 * Convenience function for callers using locales. This calls 104 * registerObject(Object, ULocale, int kind, boolean visible) 105 * passing true for the visibility. 106 */ registerObject(Object obj, ULocale locale, int kind)107 public Factory registerObject(Object obj, ULocale locale, int kind) { 108 return registerObject(obj, locale, kind, true); 109 } 110 111 /** 112 * Convenience function for callers using locales. This instantiates 113 * a SimpleLocaleKeyFactory, and registers the factory. 114 */ registerObject(Object obj, ULocale locale, int kind, boolean visible)115 public Factory registerObject(Object obj, ULocale locale, int kind, boolean visible) { 116 Factory factory = new SimpleLocaleKeyFactory(obj, locale, kind, visible); 117 return registerFactory(factory); 118 } 119 120 /** 121 * Convenience method for callers using locales. This returns the standard 122 * Locale list, built from the Set of visible ids. 123 */ getAvailableLocales()124 public Locale[] getAvailableLocales() { 125 // TODO make this wrap getAvailableULocales later 126 Set<String> visIDs = getVisibleIDs(); 127 Locale[] locales = new Locale[visIDs.size()]; 128 int n = 0; 129 for (String id : visIDs) { 130 Locale loc = LocaleUtility.getLocaleFromName(id); 131 locales[n++] = loc; 132 } 133 return locales; 134 } 135 136 /** 137 * Convenience method for callers using locales. This returns the standard 138 * ULocale list, built from the Set of visible ids. 139 */ getAvailableULocales()140 public ULocale[] getAvailableULocales() { 141 Set<String> visIDs = getVisibleIDs(); 142 ULocale[] locales = new ULocale[visIDs.size()]; 143 int n = 0; 144 for (String id : visIDs) { 145 locales[n++] = new ULocale(id); 146 } 147 return locales; 148 } 149 150 /** 151 * A subclass of Key that implements a locale fallback mechanism. 152 * The first locale to search for is the locale provided by the 153 * client, and the fallback locale to search for is the current 154 * default locale. If a prefix is present, the currentDescriptor 155 * includes it before the locale proper, separated by "/". This 156 * is the default key instantiated by ICULocaleService.</p> 157 * 158 * <p>Canonicalization adjusts the locale string so that the 159 * section before the first understore is in lower case, and the rest 160 * is in upper case, with no trailing underscores.</p> 161 */ 162 public static class LocaleKey extends ICUService.Key { 163 private int kind; 164 private int varstart; 165 private String primaryID; 166 private String fallbackID; 167 private String currentID; 168 169 public static final int KIND_ANY = -1; 170 171 /** 172 * Create a LocaleKey with canonical primary and fallback IDs. 173 */ createWithCanonicalFallback(String primaryID, String canonicalFallbackID)174 public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID) { 175 return createWithCanonicalFallback(primaryID, canonicalFallbackID, KIND_ANY); 176 } 177 178 /** 179 * Create a LocaleKey with canonical primary and fallback IDs. 180 */ createWithCanonicalFallback(String primaryID, String canonicalFallbackID, int kind)181 public static LocaleKey createWithCanonicalFallback(String primaryID, String canonicalFallbackID, int kind) { 182 if (primaryID == null) { 183 return null; 184 } 185 String canonicalPrimaryID = ULocale.getName(primaryID); 186 return new LocaleKey(primaryID, canonicalPrimaryID, canonicalFallbackID, kind); 187 } 188 189 /** 190 * Create a LocaleKey with canonical primary and fallback IDs. 191 */ createWithCanonical(ULocale locale, String canonicalFallbackID, int kind)192 public static LocaleKey createWithCanonical(ULocale locale, String canonicalFallbackID, int kind) { 193 if (locale == null) { 194 return null; 195 } 196 String canonicalPrimaryID = locale.getName(); 197 return new LocaleKey(canonicalPrimaryID, canonicalPrimaryID, canonicalFallbackID, kind); 198 } 199 200 /** 201 * PrimaryID is the user's requested locale string, 202 * canonicalPrimaryID is this string in canonical form, 203 * fallbackID is the current default locale's string in 204 * canonical form. 205 */ LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID, int kind)206 protected LocaleKey(String primaryID, String canonicalPrimaryID, String canonicalFallbackID, int kind) { 207 super(primaryID); 208 this.kind = kind; 209 210 if (canonicalPrimaryID == null || canonicalPrimaryID.equalsIgnoreCase("root")) { 211 this.primaryID = ""; 212 this.fallbackID = null; 213 } else { 214 int idx = canonicalPrimaryID.indexOf('@'); 215 if (idx == 4 && canonicalPrimaryID.regionMatches(true, 0, "root", 0, 4)) { 216 this.primaryID = canonicalPrimaryID.substring(4); 217 this.varstart = 0; 218 this.fallbackID = null; 219 } else { 220 this.primaryID = canonicalPrimaryID; 221 this.varstart = idx; 222 223 if (canonicalFallbackID == null || this.primaryID.equals(canonicalFallbackID)) { 224 this.fallbackID = ""; 225 } else { 226 this.fallbackID = canonicalFallbackID; 227 } 228 } 229 } 230 231 this.currentID = varstart == -1 ? this.primaryID : this.primaryID.substring(0, varstart); 232 } 233 234 /** 235 * Return the prefix associated with the kind, or null if the kind is KIND_ANY. 236 */ prefix()237 public String prefix() { 238 return kind == KIND_ANY ? null : Integer.toString(kind()); 239 } 240 241 /** 242 * Return the kind code associated with this key. 243 */ kind()244 public int kind() { 245 return kind; 246 } 247 248 /** 249 * Return the (canonical) original ID. 250 */ 251 @Override canonicalID()252 public String canonicalID() { 253 return primaryID; 254 } 255 256 /** 257 * Return the (canonical) current ID, or null if no current id. 258 */ 259 @Override currentID()260 public String currentID() { 261 return currentID; 262 } 263 264 /** 265 * Return the (canonical) current descriptor, or null if no current id. 266 * Includes the keywords, whereas the ID does not include keywords. 267 */ 268 @Override currentDescriptor()269 public String currentDescriptor() { 270 String result = currentID(); 271 if (result != null) { 272 StringBuilder buf = new StringBuilder(); // default capacity 16 is usually good enough 273 if (kind != KIND_ANY) { 274 buf.append(prefix()); 275 } 276 buf.append('/'); 277 buf.append(result); 278 if (varstart != -1) { 279 buf.append(primaryID.substring(varstart, primaryID.length())); 280 } 281 result = buf.toString(); 282 } 283 return result; 284 } 285 286 /** 287 * Convenience method to return the locale corresponding to the (canonical) original ID. 288 */ canonicalLocale()289 public ULocale canonicalLocale() { 290 return new ULocale(primaryID); 291 } 292 293 /** 294 * Convenience method to return the ulocale corresponding to the (canonical) currentID. 295 */ currentLocale()296 public ULocale currentLocale() { 297 if (varstart == -1) { 298 return new ULocale(currentID); 299 } else { 300 return new ULocale(currentID + primaryID.substring(varstart)); 301 } 302 } 303 304 /** 305 * If the key has a fallback, modify the key and return true, 306 * otherwise return false.</p> 307 * 308 * <p>First falls back through the primary ID, then through 309 * the fallbackID. The final fallback is "" (root) 310 * unless the primary id was "" (root), in which case 311 * there is no fallback. 312 */ 313 @Override fallback()314 public boolean fallback() { 315 int x = currentID.lastIndexOf('_'); 316 if (x != -1) { 317 while (--x >= 0 && currentID.charAt(x) == '_') { // handle zh__PINYIN 318 } 319 currentID = currentID.substring(0, x+1); 320 return true; 321 } 322 if (fallbackID != null) { 323 currentID = fallbackID; 324 if (fallbackID.length() == 0) { 325 fallbackID = null; 326 } else { 327 fallbackID = ""; 328 } 329 return true; 330 } 331 currentID = null; 332 return false; 333 } 334 335 /** 336 * If a key created from id would eventually fallback to match the 337 * canonical ID of this key, return true. 338 */ 339 @Override isFallbackOf(String id)340 public boolean isFallbackOf(String id) { 341 return LocaleUtility.isFallbackOf(canonicalID(), id); 342 } 343 } 344 345 /** 346 * A subclass of Factory that uses LocaleKeys. If 'visible' the 347 * factory reports its IDs. 348 */ 349 public static abstract class LocaleKeyFactory implements Factory { 350 protected final String name; 351 protected final boolean visible; 352 353 public static final boolean VISIBLE = true; 354 public static final boolean INVISIBLE = false; 355 356 /** 357 * Constructor used by subclasses. 358 */ LocaleKeyFactory(boolean visible)359 protected LocaleKeyFactory(boolean visible) { 360 this.visible = visible; 361 this.name = null; 362 } 363 364 /** 365 * Constructor used by subclasses. 366 */ LocaleKeyFactory(boolean visible, String name)367 protected LocaleKeyFactory(boolean visible, String name) { 368 this.visible = visible; 369 this.name = name; 370 } 371 372 /** 373 * Implement superclass abstract method. This checks the currentID of 374 * the key against the supported IDs, and passes the canonicalLocale and 375 * kind off to handleCreate (which subclasses must implement). 376 */ 377 @Override create(Key key, ICUService service)378 public Object create(Key key, ICUService service) { 379 if (handlesKey(key)) { 380 LocaleKey lkey = (LocaleKey)key; 381 int kind = lkey.kind(); 382 383 ULocale uloc = lkey.currentLocale(); 384 return handleCreate(uloc, kind, service); 385 } else { 386 // System.out.println("factory: " + this + " did not support id: " + key.currentID()); 387 // System.out.println("supported ids: " + getSupportedIDs()); 388 } 389 return null; 390 } 391 handlesKey(Key key)392 protected boolean handlesKey(Key key) { 393 if (key != null) { 394 String id = key.currentID(); 395 Set<String> supported = getSupportedIDs(); 396 return supported.contains(id); 397 } 398 return false; 399 } 400 401 /** 402 * Override of superclass method. 403 */ 404 @Override updateVisibleIDs(Map<String, Factory> result)405 public void updateVisibleIDs(Map<String, Factory> result) { 406 Set<String> cache = getSupportedIDs(); 407 for (String id : cache) { 408 if (visible) { 409 result.put(id, this); 410 } else { 411 result.remove(id); 412 } 413 } 414 } 415 416 /** 417 * Return a localized name for the locale represented by id. 418 */ 419 @Override getDisplayName(String id, ULocale locale)420 public String getDisplayName(String id, ULocale locale) { 421 // assume if the user called this on us, we must have handled some fallback of this id 422 // if (isSupportedID(id)) { 423 if (locale == null) { 424 return id; 425 } 426 ULocale loc = new ULocale(id); 427 return loc.getDisplayName(locale); 428 // } 429 // return null; 430 } 431 432 ///CLOVER:OFF 433 /** 434 * Utility method used by create(Key, ICUService). Subclasses can 435 * implement this instead of create. 436 */ handleCreate(ULocale loc, int kind, ICUService service)437 protected Object handleCreate(ULocale loc, int kind, ICUService service) { 438 return null; 439 } 440 ///CLOVER:ON 441 442 /** 443 * Return true if this id is one the factory supports (visible or 444 * otherwise). 445 */ isSupportedID(String id)446 protected boolean isSupportedID(String id) { 447 return getSupportedIDs().contains(id); 448 } 449 450 /** 451 * Return the set of ids that this factory supports (visible or 452 * otherwise). This can be called often and might need to be 453 * cached if it is expensive to create. 454 */ getSupportedIDs()455 protected Set<String> getSupportedIDs() { 456 return Collections.emptySet(); 457 } 458 459 /** 460 * For debugging. 461 */ 462 @Override toString()463 public String toString() { 464 StringBuilder buf = new StringBuilder(super.toString()); 465 if (name != null) { 466 buf.append(", name: "); 467 buf.append(name); 468 } 469 buf.append(", visible: "); 470 buf.append(visible); 471 return buf.toString(); 472 } 473 } 474 475 /** 476 * A LocaleKeyFactory that just returns a single object for a kind/locale. 477 */ 478 public static class SimpleLocaleKeyFactory extends LocaleKeyFactory { 479 private final Object obj; 480 private final String id; 481 private final int kind; 482 483 // TODO: remove when we no longer need this SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible)484 public SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible) { 485 this(obj, locale, kind, visible, null); 486 } 487 SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible, String name)488 public SimpleLocaleKeyFactory(Object obj, ULocale locale, int kind, boolean visible, String name) { 489 super(visible, name); 490 491 this.obj = obj; 492 this.id = locale.getBaseName(); 493 this.kind = kind; 494 } 495 496 /** 497 * Returns the service object if kind/locale match. Service is not used. 498 */ 499 @Override create(Key key, ICUService service)500 public Object create(Key key, ICUService service) { 501 if (!(key instanceof LocaleKey)) { 502 return null; 503 } 504 505 LocaleKey lkey = (LocaleKey)key; 506 if (kind != LocaleKey.KIND_ANY && kind != lkey.kind()) { 507 return null; 508 } 509 if (!id.equals(lkey.currentID())) { 510 return null; 511 } 512 513 return obj; 514 } 515 516 @Override isSupportedID(String idToCheck)517 protected boolean isSupportedID(String idToCheck) { 518 return this.id.equals(idToCheck); 519 } 520 521 @Override updateVisibleIDs(Map<String, Factory> result)522 public void updateVisibleIDs(Map<String, Factory> result) { 523 if (visible) { 524 result.put(id, this); 525 } else { 526 result.remove(id); 527 } 528 } 529 530 @Override toString()531 public String toString() { 532 StringBuilder buf = new StringBuilder(super.toString()); 533 buf.append(", id: "); 534 buf.append(id); 535 buf.append(", kind: "); 536 buf.append(kind); 537 return buf.toString(); 538 } 539 } 540 541 /** 542 * A LocaleKeyFactory that creates a service based on the ICU locale data. 543 * This is a base class for most ICU factories. Subclasses instantiate it 544 * with a constructor that takes a bundle name, which determines the supported 545 * IDs. Subclasses then override handleCreate to create the actual service 546 * object. The default implementation returns a resource bundle. 547 */ 548 public static class ICUResourceBundleFactory extends LocaleKeyFactory { 549 protected final String bundleName; 550 551 /** 552 * Convenience constructor that uses the main ICU bundle name. 553 */ ICUResourceBundleFactory()554 public ICUResourceBundleFactory() { 555 this(ICUData.ICU_BASE_NAME); 556 } 557 558 /** 559 * A service factory based on ICU resource data in resources 560 * with the given name. 561 */ ICUResourceBundleFactory(String bundleName)562 public ICUResourceBundleFactory(String bundleName) { 563 super(true); 564 565 this.bundleName = bundleName; 566 } 567 568 /** 569 * Return the supported IDs. This is the set of all locale names for the bundleName. 570 */ 571 @Override getSupportedIDs()572 protected Set<String> getSupportedIDs() { 573 return ICUResourceBundle.getFullLocaleNameSet(bundleName, loader()); 574 } 575 576 /** 577 * Override of superclass method. 578 */ 579 @Override updateVisibleIDs(Map<String, Factory> result)580 public void updateVisibleIDs(Map<String, Factory> result) { 581 Set<String> visibleIDs = ICUResourceBundle.getAvailableLocaleNameSet(bundleName, loader()); // only visible ids 582 for (String id : visibleIDs) { 583 result.put(id, this); 584 } 585 } 586 587 /** 588 * Create the service. The default implementation returns the resource bundle 589 * for the locale, ignoring kind, and service. 590 */ 591 @Override handleCreate(ULocale loc, int kind, ICUService service)592 protected Object handleCreate(ULocale loc, int kind, ICUService service) { 593 return ICUResourceBundle.getBundleInstance(bundleName, loc, loader()); 594 } 595 loader()596 protected ClassLoader loader() { 597 return ClassLoaderUtil.getClassLoader(getClass()); 598 } 599 600 @Override toString()601 public String toString() { 602 return super.toString() + ", bundle: " + bundleName; 603 } 604 } 605 606 /** 607 * Return the name of the current fallback locale. If it has changed since this was 608 * last accessed, the service cache is cleared. 609 */ validateFallbackLocale()610 public String validateFallbackLocale() { 611 ULocale loc = ULocale.getDefault(); 612 if (loc != fallbackLocale) { 613 synchronized (this) { 614 if (loc != fallbackLocale) { 615 fallbackLocale = loc; 616 fallbackLocaleName = loc.getBaseName(); 617 clearServiceCache(); 618 } 619 } 620 } 621 return fallbackLocaleName; 622 } 623 624 @Override createKey(String id)625 public Key createKey(String id) { 626 return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale()); 627 } 628 createKey(String id, int kind)629 public Key createKey(String id, int kind) { 630 return LocaleKey.createWithCanonicalFallback(id, validateFallbackLocale(), kind); 631 } 632 createKey(ULocale l, int kind)633 public Key createKey(ULocale l, int kind) { 634 return LocaleKey.createWithCanonical(l, validateFallbackLocale(), kind); 635 } 636 } 637