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