1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 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.ArrayList; 12 import java.util.Collections; 13 import java.util.Comparator; 14 import java.util.EventListener; 15 import java.util.HashMap; 16 import java.util.HashSet; 17 import java.util.Iterator; 18 import java.util.List; 19 import java.util.ListIterator; 20 import java.util.Map; 21 import java.util.Map.Entry; 22 import java.util.Set; 23 import java.util.SortedMap; 24 import java.util.TreeMap; 25 import java.util.concurrent.ConcurrentHashMap; 26 27 import com.ibm.icu.util.ULocale; 28 import com.ibm.icu.util.ULocale.Category; 29 30 /** 31 * <p>A Service provides access to service objects that implement a 32 * particular service, e.g. transliterators. Users provide a String 33 * id (for example, a locale string) to the service, and get back an 34 * object for that id. Service objects can be any kind of object. 35 * The service object is cached and returned for later queries, so 36 * generally it should not be mutable, or the caller should clone the 37 * object before modifying it.</p> 38 * 39 * <p>Services 'canonicalize' the query id and use the canonical id to 40 * query for the service. The service also defines a mechanism to 41 * 'fallback' the id multiple times. Clients can optionally request 42 * the actual id that was matched by a query when they use an id to 43 * retrieve a service object.</p> 44 * 45 * <p>Service objects are instantiated by Factory objects registered with 46 * the service. The service queries each Factory in turn, from most recently 47 * registered to earliest registered, until one returns a service object. 48 * If none responds with a service object, a fallback id is generated, 49 * and the process repeats until a service object is returned or until 50 * the id has no further fallbacks.</p> 51 * 52 * <p>Factories can be dynamically registered and unregistered with the 53 * service. When registered, a Factory is installed at the head of 54 * the factory list, and so gets 'first crack' at any keys or fallback 55 * keys. When unregistered, it is removed from the service and can no 56 * longer be located through it. Service objects generated by this 57 * factory and held by the client are unaffected.</p> 58 * 59 * <p>ICUService uses Keys to query factories and perform 60 * fallback. The Key defines the canonical form of the id, and 61 * implements the fallback strategy. Custom Keys can be defined that 62 * parse complex IDs into components that Factories can more easily 63 * use. The Key can cache the results of this parsing to save 64 * repeated effort. ICUService provides convenience APIs that 65 * take Strings and generate default Keys for use in querying.</p> 66 * 67 * <p>ICUService provides API to get the list of ids publicly 68 * supported by the service (although queries aren't restricted to 69 * this list). This list contains only 'simple' IDs, and not fully 70 * unique ids. Factories are associated with each simple ID and 71 * the responsible factory can also return a human-readable localized 72 * version of the simple ID, for use in user interfaces. ICUService 73 * can also provide a sorted collection of the all the localized visible 74 * ids.</p> 75 * 76 * <p>ICUService implements ICUNotifier, so that clients can register 77 * to receive notification when factories are added or removed from 78 * the service. ICUService provides a default EventListener subinterface, 79 * ServiceListener, which can be registered with the service. When 80 * the service changes, the ServiceListener's serviceChanged method 81 * is called, with the service as the only argument.</p> 82 * 83 * <p>The ICUService API is both rich and generic, and it is expected 84 * that most implementations will statically 'wrap' ICUService to 85 * present a more appropriate API-- for example, to declare the type 86 * of the objects returned from get, to limit the factories that can 87 * be registered with the service, or to define their own listener 88 * interface with a custom callback method. They might also customize 89 * ICUService by overriding it, for example, to customize the Key and 90 * fallback strategy. ICULocaleService is a customized service that 91 * uses Locale names as ids and uses Keys that implement the standard 92 * resource bundle fallback strategy.<p> 93 */ 94 public class ICUService extends ICUNotifier { 95 /** 96 * Name used for debugging. 97 */ 98 protected final String name; 99 100 /** 101 * Constructor. 102 */ ICUService()103 public ICUService() { 104 name = ""; 105 } 106 107 private static final boolean DEBUG = ICUDebug.enabled("service"); 108 /** 109 * Construct with a name (useful for debugging). 110 */ ICUService(String name)111 public ICUService(String name) { 112 this.name = name; 113 } 114 115 /** 116 * Access to factories is protected by a read-write lock. This is 117 * to allow multiple threads to read concurrently, but keep 118 * changes to the factory list atomic with respect to all readers. 119 */ 120 private final ICURWLock factoryLock = new ICURWLock(); 121 122 /** 123 * All the factories registered with this service. 124 */ 125 private final List<Factory> factories = new ArrayList<Factory>(); 126 127 /** 128 * Record the default number of factories for this service. 129 * Can be set by markDefault. 130 */ 131 private int defaultSize = 0; 132 133 /** 134 * Keys are used to communicate with factories to generate an 135 * instance of the service. Keys define how ids are 136 * canonicalized, provide both a current id and a current 137 * descriptor to use in querying the cache and factories, and 138 * determine the fallback strategy.</p> 139 * 140 * <p>Keys provide both a currentDescriptor and a currentID. 141 * The descriptor contains an optional prefix, followed by '/' 142 * and the currentID. Factories that handle complex keys, 143 * for example number format factories that generate multiple 144 * kinds of formatters for the same locale, use the descriptor 145 * to provide a fully unique identifier for the service object, 146 * while using the currentID (in this case, the locale string), 147 * as the visible IDs that can be localized. 148 * 149 * <p> The default implementation of Key has no fallbacks and 150 * has no custom descriptors.</p> 151 */ 152 public static class Key { 153 private final String id; 154 155 /** 156 * Construct a key from an id. 157 */ Key(String id)158 public Key(String id) { 159 this.id = id; 160 } 161 162 /** 163 * Return the original ID used to construct this key. 164 */ id()165 public final String id() { 166 return id; 167 } 168 169 /** 170 * Return the canonical version of the original ID. This implementation 171 * returns the original ID unchanged. 172 */ canonicalID()173 public String canonicalID() { 174 return id; 175 } 176 177 /** 178 * Return the (canonical) current ID. This implementation 179 * returns the canonical ID. 180 */ currentID()181 public String currentID() { 182 return canonicalID(); 183 } 184 185 /** 186 * Return the current descriptor. This implementation returns 187 * the current ID. The current descriptor is used to fully 188 * identify an instance of the service in the cache. A 189 * factory may handle all descriptors for an ID, or just a 190 * particular descriptor. The factory can either parse the 191 * descriptor or use custom API on the key in order to 192 * instantiate the service. 193 */ currentDescriptor()194 public String currentDescriptor() { 195 return "/" + currentID(); 196 } 197 198 /** 199 * If the key has a fallback, modify the key and return true, 200 * otherwise return false. The current ID will change if there 201 * is a fallback. No currentIDs should be repeated, and fallback 202 * must eventually return false. This implementation has no fallbacks 203 * and always returns false. 204 */ fallback()205 public boolean fallback() { 206 return false; 207 } 208 209 /** 210 * If a key created from id would eventually fallback to match the 211 * canonical ID of this key, return true. 212 */ isFallbackOf(String idToCheck)213 public boolean isFallbackOf(String idToCheck) { 214 return canonicalID().equals(idToCheck); 215 } 216 } 217 218 /** 219 * Factories generate the service objects maintained by the 220 * service. A factory generates a service object from a key, 221 * updates id->factory mappings, and returns the display name for 222 * a supported id. 223 */ 224 public static interface Factory { 225 226 /** 227 * Create a service object from the key, if this factory 228 * supports the key. Otherwise, return null. 229 * 230 * <p>If the factory supports the key, then it can call 231 * the service's getKey(Key, String[], Factory) method 232 * passing itself as the factory to get the object that 233 * the service would have created prior to the factory's 234 * registration with the service. This can change the 235 * key, so any information required from the key should 236 * be extracted before making such a callback. 237 */ create(Key key, ICUService service)238 public Object create(Key key, ICUService service); 239 240 /** 241 * Update the result IDs (not descriptors) to reflect the IDs 242 * this factory handles. This function and getDisplayName are 243 * used to support ICUService.getDisplayNames. Basically, the 244 * factory has to determine which IDs it will permit to be 245 * available, and of those, which it will provide localized 246 * display names for. In most cases this reflects the IDs that 247 * the factory directly supports. 248 */ updateVisibleIDs(Map<String, Factory> result)249 public void updateVisibleIDs(Map<String, Factory> result); 250 251 /** 252 * Return the display name for this id in the provided locale. 253 * This is an localized id, not a descriptor. If the id is 254 * not visible or not defined by the factory, return null. 255 * If locale is null, return id unchanged. 256 */ getDisplayName(String id, ULocale locale)257 public String getDisplayName(String id, ULocale locale); 258 } 259 260 /** 261 * A default implementation of factory. This provides default 262 * implementations for subclasses, and implements a singleton 263 * factory that matches a single id and returns a single 264 * (possibly deferred-initialized) instance. This implements 265 * updateVisibleIDs to add a mapping from its ID to itself 266 * if visible is true, or to remove any existing mapping 267 * for its ID if visible is false. 268 */ 269 public static class SimpleFactory implements Factory { 270 protected Object instance; 271 protected String id; 272 protected boolean visible; 273 274 /** 275 * Convenience constructor that calls SimpleFactory(Object, String, boolean) 276 * with visible true. 277 */ SimpleFactory(Object instance, String id)278 public SimpleFactory(Object instance, String id) { 279 this(instance, id, true); 280 } 281 282 /** 283 * Construct a simple factory that maps a single id to a single 284 * service instance. If visible is true, the id will be visible. 285 * Neither the instance nor the id can be null. 286 */ SimpleFactory(Object instance, String id, boolean visible)287 public SimpleFactory(Object instance, String id, boolean visible) { 288 if (instance == null || id == null) { 289 throw new IllegalArgumentException("Instance or id is null"); 290 } 291 this.instance = instance; 292 this.id = id; 293 this.visible = visible; 294 } 295 296 /** 297 * Return the service instance if the factory's id is equal to 298 * the key's currentID. Service is ignored. 299 */ 300 @Override create(Key key, ICUService service)301 public Object create(Key key, ICUService service) { 302 if (id.equals(key.currentID())) { 303 return instance; 304 } 305 return null; 306 } 307 308 /** 309 * If visible, adds a mapping from id -> this to the result, 310 * otherwise removes id from result. 311 */ 312 @Override updateVisibleIDs(Map<String, Factory> result)313 public void updateVisibleIDs(Map<String, Factory> result) { 314 if (visible) { 315 result.put(id, this); 316 } else { 317 result.remove(id); 318 } 319 } 320 321 /** 322 * If this.id equals id, returns id regardless of locale, 323 * otherwise returns null. (This default implementation has 324 * no localized id information.) 325 */ 326 @Override getDisplayName(String identifier, ULocale locale)327 public String getDisplayName(String identifier, ULocale locale) { 328 return (visible && id.equals(identifier)) ? identifier : null; 329 } 330 331 /** 332 * For debugging. 333 */ 334 @Override toString()335 public String toString() { 336 StringBuilder buf = new StringBuilder(super.toString()); 337 buf.append(", id: "); 338 buf.append(id); 339 buf.append(", visible: "); 340 buf.append(visible); 341 return buf.toString(); 342 } 343 } 344 345 /** 346 * Convenience override for get(String, String[]). This uses 347 * createKey to create a key for the provided descriptor. 348 */ get(String descriptor)349 public Object get(String descriptor) { 350 return getKey(createKey(descriptor), null); 351 } 352 353 /** 354 * Convenience override for get(Key, String[]). This uses 355 * createKey to create a key from the provided descriptor. 356 */ get(String descriptor, String[] actualReturn)357 public Object get(String descriptor, String[] actualReturn) { 358 if (descriptor == null) { 359 throw new NullPointerException("descriptor must not be null"); 360 } 361 return getKey(createKey(descriptor), actualReturn); 362 } 363 364 /** 365 * Convenience override for get(Key, String[]). 366 */ getKey(Key key)367 public Object getKey(Key key) { 368 return getKey(key, null); 369 } 370 371 /** 372 * <p>Given a key, return a service object, and, if actualReturn 373 * is not null, the descriptor with which it was found in the 374 * first element of actualReturn. If no service object matches 375 * this key, return null, and leave actualReturn unchanged.</p> 376 * 377 * <p>This queries the cache using the key's descriptor, and if no 378 * object in the cache matches it, tries the key on each 379 * registered factory, in order. If none generates a service 380 * object for the key, repeats the process with each fallback of 381 * the key, until either one returns a service object, or the key 382 * has no fallback.</p> 383 * 384 * <p>If key is null, just returns null.</p> 385 */ getKey(Key key, String[] actualReturn)386 public Object getKey(Key key, String[] actualReturn) { 387 return getKey(key, actualReturn, null); 388 } 389 390 // debugging 391 // Map hardRef; 392 getKey(Key key, String[] actualReturn, Factory factory)393 public Object getKey(Key key, String[] actualReturn, Factory factory) { 394 if (factories.size() == 0) { 395 return handleDefault(key, actualReturn); 396 } 397 398 if (DEBUG) System.out.println("Service: " + name + " key: " + key.canonicalID()); 399 400 CacheEntry result = null; 401 if (key != null) { 402 try { 403 // The factory list can't be modified until we're done, 404 // otherwise we might update the cache with an invalid result. 405 // The cache has to stay in synch with the factory list. 406 factoryLock.acquireRead(); 407 408 Map<String, CacheEntry> cache = this.cache; // copy so we don't need to sync on this 409 if (cache == null) { 410 if (DEBUG) System.out.println("Service " + name + " cache was empty"); 411 // synchronized since additions and queries on the cache must be atomic 412 // they can be interleaved, though 413 cache = new ConcurrentHashMap<String, CacheEntry>(); 414 } 415 416 String currentDescriptor = null; 417 ArrayList<String> cacheDescriptorList = null; 418 boolean putInCache = false; 419 420 int NDebug = 0; 421 422 int startIndex = 0; 423 int limit = factories.size(); 424 boolean cacheResult = true; 425 if (factory != null) { 426 for (int i = 0; i < limit; ++i) { 427 if (factory == factories.get(i)) { 428 startIndex = i + 1; 429 break; 430 } 431 } 432 if (startIndex == 0) { 433 throw new IllegalStateException("Factory " + factory + "not registered with service: " + this); 434 } 435 cacheResult = false; 436 } 437 438 outer: 439 do { 440 currentDescriptor = key.currentDescriptor(); 441 if (DEBUG) System.out.println(name + "[" + NDebug++ + "] looking for: " + currentDescriptor); 442 result = cache.get(currentDescriptor); 443 if (result != null) { 444 if (DEBUG) System.out.println(name + " found with descriptor: " + currentDescriptor); 445 break outer; 446 } else { 447 if (DEBUG) System.out.println("did not find: " + currentDescriptor + " in cache"); 448 } 449 450 // first test of cache failed, so we'll have to update 451 // the cache if we eventually succeed-- that is, if we're 452 // going to update the cache at all. 453 putInCache = cacheResult; 454 455 // int n = 0; 456 int index = startIndex; 457 while (index < limit) { 458 Factory f = factories.get(index++); 459 if (DEBUG) System.out.println("trying factory[" + (index-1) + "] " + f.toString()); 460 Object service = f.create(key, this); 461 if (service != null) { 462 result = new CacheEntry(currentDescriptor, service); 463 if (DEBUG) System.out.println(name + " factory supported: " + currentDescriptor + ", caching"); 464 break outer; 465 } else { 466 if (DEBUG) System.out.println("factory did not support: " + currentDescriptor); 467 } 468 } 469 470 // prepare to load the cache with all additional ids that 471 // will resolve to result, assuming we'll succeed. We 472 // don't want to keep querying on an id that's going to 473 // fallback to the one that succeeded, we want to hit the 474 // cache the first time next goaround. 475 if (cacheDescriptorList == null) { 476 cacheDescriptorList = new ArrayList<String>(5); 477 } 478 cacheDescriptorList.add(currentDescriptor); 479 480 } while (key.fallback()); 481 482 if (result != null) { 483 if (putInCache) { 484 if (DEBUG) System.out.println("caching '" + result.actualDescriptor + "'"); 485 cache.put(result.actualDescriptor, result); 486 if (cacheDescriptorList != null) { 487 for (String desc : cacheDescriptorList) { 488 if (DEBUG) System.out.println(name + " adding descriptor: '" + desc + "' for actual: '" + result.actualDescriptor + "'"); 489 490 cache.put(desc, result); 491 } 492 } 493 // Atomic update. We held the read lock all this time 494 // so we know our cache is consistent with the factory list. 495 // We might stomp over a cache that some other thread 496 // rebuilt, but that's the breaks. They're both good. 497 this.cache = cache; 498 } 499 500 if (actualReturn != null) { 501 // strip null prefix 502 if (result.actualDescriptor.indexOf("/") == 0) { 503 actualReturn[0] = result.actualDescriptor.substring(1); 504 } else { 505 actualReturn[0] = result.actualDescriptor; 506 } 507 } 508 509 if (DEBUG) System.out.println("found in service: " + name); 510 511 return result.service; 512 } 513 } 514 finally { 515 factoryLock.releaseRead(); 516 } 517 } 518 519 if (DEBUG) System.out.println("not found in service: " + name); 520 521 return handleDefault(key, actualReturn); 522 } 523 private Map<String, CacheEntry> cache; 524 525 // Record the actual id for this service in the cache, so we can return it 526 // even if we succeed later with a different id. 527 private static final class CacheEntry { 528 final String actualDescriptor; 529 final Object service; CacheEntry(String actualDescriptor, Object service)530 CacheEntry(String actualDescriptor, Object service) { 531 this.actualDescriptor = actualDescriptor; 532 this.service = service; 533 } 534 } 535 536 537 /** 538 * Default handler for this service if no factory in the list 539 * handled the key. 540 */ handleDefault(Key key, String[] actualIDReturn)541 protected Object handleDefault(Key key, String[] actualIDReturn) { 542 return null; 543 } 544 545 /** 546 * Convenience override for getVisibleIDs(String) that passes null 547 * as the fallback, thus returning all visible IDs. 548 */ getVisibleIDs()549 public Set<String> getVisibleIDs() { 550 return getVisibleIDs(null); 551 } 552 553 /** 554 * <p>Return a snapshot of the visible IDs for this service. This 555 * set will not change as Factories are added or removed, but the 556 * supported ids will, so there is no guarantee that all and only 557 * the ids in the returned set are visible and supported by the 558 * service in subsequent calls.</p> 559 * 560 * <p>matchID is passed to createKey to create a key. If the 561 * key is not null, it is used to filter out ids that don't have 562 * the key as a fallback. 563 */ getVisibleIDs(String matchID)564 public Set<String> getVisibleIDs(String matchID) { 565 Set<String> result = getVisibleIDMap().keySet(); 566 567 Key fallbackKey = createKey(matchID); 568 569 if (fallbackKey != null) { 570 Set<String> temp = new HashSet<String>(result.size()); 571 for (String id : result) { 572 if (fallbackKey.isFallbackOf(id)) { 573 temp.add(id); 574 } 575 } 576 result = temp; 577 } 578 return result; 579 } 580 581 /** 582 * Return a map from visible ids to factories. 583 */ getVisibleIDMap()584 private Map<String, Factory> getVisibleIDMap() { 585 synchronized (this) { // or idcache-only lock? 586 if (idcache == null) { 587 try { 588 factoryLock.acquireRead(); 589 Map<String, Factory> mutableMap = new HashMap<String, Factory>(); 590 ListIterator<Factory> lIter = factories.listIterator(factories.size()); 591 while (lIter.hasPrevious()) { 592 Factory f = lIter.previous(); 593 f.updateVisibleIDs(mutableMap); 594 } 595 // Capture the return value in a local variable. 596 // Avoids returning an idcache value changed by another thread (could be null after clearCaches()). 597 Map<String, Factory> result = Collections.unmodifiableMap(mutableMap); 598 this.idcache = result; 599 return result; 600 } finally { 601 factoryLock.releaseRead(); 602 } 603 } 604 return idcache; 605 } 606 } 607 private Map<String, Factory> idcache; 608 609 /** 610 * Convenience override for getDisplayName(String, ULocale) that 611 * uses the current default locale. 612 */ getDisplayName(String id)613 public String getDisplayName(String id) { 614 return getDisplayName(id, ULocale.getDefault(Category.DISPLAY)); 615 } 616 617 /** 618 * Given a visible id, return the display name in the requested locale. 619 * If there is no directly supported id corresponding to this id, return 620 * null. 621 */ getDisplayName(String id, ULocale locale)622 public String getDisplayName(String id, ULocale locale) { 623 Map<String, Factory> m = getVisibleIDMap(); 624 Factory f = m.get(id); 625 if (f != null) { 626 return f.getDisplayName(id, locale); 627 } 628 629 Key key = createKey(id); 630 while (key.fallback()) { 631 f = m.get(key.currentID()); 632 if (f != null) { 633 return f.getDisplayName(id, locale); 634 } 635 } 636 637 return null; 638 } 639 640 /** 641 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 642 * uses the current default Locale as the locale, null as 643 * the comparator, and null for the matchID. 644 */ getDisplayNames()645 public SortedMap<String, String> getDisplayNames() { 646 ULocale locale = ULocale.getDefault(Category.DISPLAY); 647 return getDisplayNames(locale, null, null); 648 } 649 650 /** 651 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 652 * uses null for the comparator, and null for the matchID. 653 */ getDisplayNames(ULocale locale)654 public SortedMap<String, String> getDisplayNames(ULocale locale) { 655 return getDisplayNames(locale, null, null); 656 } 657 658 /** 659 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 660 * uses null for the matchID, thus returning all display names. 661 */ getDisplayNames(ULocale locale, Comparator<Object> com)662 public SortedMap<String, String> getDisplayNames(ULocale locale, Comparator<Object> com) { 663 return getDisplayNames(locale, com, null); 664 } 665 666 /** 667 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 668 * uses null for the comparator. 669 */ getDisplayNames(ULocale locale, String matchID)670 public SortedMap<String, String> getDisplayNames(ULocale locale, String matchID) { 671 return getDisplayNames(locale, null, matchID); 672 } 673 674 /** 675 * Return a snapshot of the mapping from display names to visible 676 * IDs for this service. This set will not change as factories 677 * are added or removed, but the supported ids will, so there is 678 * no guarantee that all and only the ids in the returned map will 679 * be visible and supported by the service in subsequent calls, 680 * nor is there any guarantee that the current display names match 681 * those in the set. The display names are sorted based on the 682 * comparator provided. 683 */ getDisplayNames(ULocale locale, Comparator<Object> com, String matchID)684 public SortedMap<String, String> getDisplayNames(ULocale locale, Comparator<Object> com, String matchID) { 685 SortedMap<String, String> dncache = null; 686 LocaleRef ref = dnref; 687 688 if (ref != null) { 689 dncache = ref.get(locale, com); 690 } 691 692 while (dncache == null) { 693 synchronized (this) { 694 if (ref == dnref || dnref == null) { 695 dncache = new TreeMap<String, String>(com); // sorted 696 697 Map<String, Factory> m = getVisibleIDMap(); 698 Iterator<Entry<String, Factory>> ei = m.entrySet().iterator(); 699 while (ei.hasNext()) { 700 Entry<String, Factory> e = ei.next(); 701 String id = e.getKey(); 702 Factory f = e.getValue(); 703 dncache.put(f.getDisplayName(id, locale), id); 704 } 705 706 dncache = Collections.unmodifiableSortedMap(dncache); 707 dnref = new LocaleRef(dncache, locale, com); 708 } else { 709 ref = dnref; 710 dncache = ref.get(locale, com); 711 } 712 } 713 } 714 715 Key matchKey = createKey(matchID); 716 if (matchKey == null) { 717 return dncache; 718 } 719 720 SortedMap<String, String> result = new TreeMap<String, String>(dncache); 721 Iterator<Entry<String, String>> iter = result.entrySet().iterator(); 722 while (iter.hasNext()) { 723 Entry<String, String> e = iter.next(); 724 if (!matchKey.isFallbackOf(e.getValue())) { 725 iter.remove(); 726 } 727 } 728 return result; 729 } 730 731 // we define a class so we get atomic simultaneous access to the 732 // locale, comparator, and corresponding map. 733 private static class LocaleRef { 734 private final ULocale locale; 735 private SortedMap<String, String> dnCache; 736 private Comparator<Object> com; 737 LocaleRef(SortedMap<String, String> dnCache, ULocale locale, Comparator<Object> com)738 LocaleRef(SortedMap<String, String> dnCache, ULocale locale, Comparator<Object> com) { 739 this.locale = locale; 740 this.com = com; 741 this.dnCache = dnCache; 742 } 743 744 get(ULocale loc, Comparator<Object> comp)745 SortedMap<String, String> get(ULocale loc, Comparator<Object> comp) { 746 SortedMap<String, String> m = dnCache; 747 if (m != null && 748 this.locale.equals(loc) && 749 (this.com == comp || (this.com != null && this.com.equals(comp)))) { 750 751 return m; 752 } 753 return null; 754 } 755 } 756 private LocaleRef dnref; 757 758 /** 759 * Return a snapshot of the currently registered factories. There 760 * is no guarantee that the list will still match the current 761 * factory list of the service subsequent to this call. 762 */ factories()763 public final List<Factory> factories() { 764 try { 765 factoryLock.acquireRead(); 766 return new ArrayList<Factory>(factories); 767 } 768 finally{ 769 factoryLock.releaseRead(); 770 } 771 } 772 773 /** 774 * A convenience override of registerObject(Object, String, boolean) 775 * that defaults visible to true. 776 */ registerObject(Object obj, String id)777 public Factory registerObject(Object obj, String id) { 778 return registerObject(obj, id, true); 779 } 780 781 /** 782 * Register an object with the provided id. The id will be 783 * canonicalized. The canonicalized ID will be returned by 784 * getVisibleIDs if visible is true. 785 */ registerObject(Object obj, String id, boolean visible)786 public Factory registerObject(Object obj, String id, boolean visible) { 787 String canonicalID = createKey(id).canonicalID(); 788 return registerFactory(new SimpleFactory(obj, canonicalID, visible)); 789 } 790 791 /** 792 * Register a Factory. Returns the factory if the service accepts 793 * the factory, otherwise returns null. The default implementation 794 * accepts all factories. 795 */ registerFactory(Factory factory)796 public final Factory registerFactory(Factory factory) { 797 if (factory == null) { 798 throw new NullPointerException(); 799 } 800 try { 801 factoryLock.acquireWrite(); 802 factories.add(0, factory); 803 clearCaches(); 804 } 805 finally { 806 factoryLock.releaseWrite(); 807 } 808 notifyChanged(); 809 return factory; 810 } 811 812 /** 813 * Unregister a factory. The first matching registered factory will 814 * be removed from the list. Returns true if a matching factory was 815 * removed. 816 */ unregisterFactory(Factory factory)817 public final boolean unregisterFactory(Factory factory) { 818 if (factory == null) { 819 throw new NullPointerException(); 820 } 821 822 boolean result = false; 823 try { 824 factoryLock.acquireWrite(); 825 if (factories.remove(factory)) { 826 result = true; 827 clearCaches(); 828 } 829 } 830 finally { 831 factoryLock.releaseWrite(); 832 } 833 834 if (result) { 835 notifyChanged(); 836 } 837 return result; 838 } 839 840 /** 841 * Reset the service to the default factories. The factory 842 * lock is acquired and then reInitializeFactories is called. 843 */ reset()844 public final void reset() { 845 try { 846 factoryLock.acquireWrite(); 847 reInitializeFactories(); 848 clearCaches(); 849 } 850 finally { 851 factoryLock.releaseWrite(); 852 } 853 notifyChanged(); 854 } 855 856 /** 857 * Reinitialize the factory list to its default state. By default 858 * this clears the list. Subclasses can override to provide other 859 * default initialization of the factory list. Subclasses must 860 * not call this method directly, as it must only be called while 861 * holding write access to the factory list. 862 */ reInitializeFactories()863 protected void reInitializeFactories() { 864 factories.clear(); 865 } 866 867 /** 868 * Return true if the service is in its default state. The default 869 * implementation returns true if there are no factories registered. 870 */ isDefault()871 public boolean isDefault() { 872 return factories.size() == defaultSize; 873 } 874 875 /** 876 * Set the default size to the current number of registered factories. 877 * Used by subclasses to customize the behavior of isDefault. 878 */ markDefault()879 protected void markDefault() { 880 defaultSize = factories.size(); 881 } 882 883 /** 884 * Create a key from an id. This creates a Key instance. 885 * Subclasses can override to define more useful keys appropriate 886 * to the factories they accept. If id is null, returns null. 887 */ createKey(String id)888 public Key createKey(String id) { 889 return id == null ? null : new Key(id); 890 } 891 892 /** 893 * Clear caches maintained by this service. Subclasses can 894 * override if they implement additional that need to be cleared 895 * when the service changes. Subclasses should generally not call 896 * this method directly, as it must only be called while 897 * synchronized on this. 898 */ clearCaches()899 protected void clearCaches() { 900 // we don't synchronize on these because methods that use them 901 // copy before use, and check for changes if they modify the 902 // caches. 903 cache = null; 904 idcache = null; 905 dnref = null; 906 } 907 908 /** 909 * Clears only the service cache. 910 * This can be called by subclasses when a change affects the service 911 * cache but not the id caches, e.g., when the default locale changes 912 * the resolution of ids changes, but not the visible ids themselves. 913 */ clearServiceCache()914 protected void clearServiceCache() { 915 cache = null; 916 } 917 918 /** 919 * ServiceListener is the listener that ICUService provides by default. 920 * ICUService will notify this listener when factories are added to 921 * or removed from the service. Subclasses can provide 922 * different listener interfaces that extend EventListener, and modify 923 * acceptsListener and notifyListener as appropriate. 924 */ 925 public static interface ServiceListener extends EventListener { serviceChanged(ICUService service)926 public void serviceChanged(ICUService service); 927 } 928 929 /** 930 * Return true if the listener is accepted; by default this 931 * requires a ServiceListener. Subclasses can override to accept 932 * different listeners. 933 */ 934 @Override acceptsListener(EventListener l)935 protected boolean acceptsListener(EventListener l) { 936 return l instanceof ServiceListener; 937 } 938 939 /** 940 * Notify the listener, which by default is a ServiceListener. 941 * Subclasses can override to use a different listener. 942 */ 943 @Override notifyListener(EventListener l)944 protected void notifyListener(EventListener l) { 945 ((ServiceListener)l).serviceChanged(this); 946 } 947 948 /** 949 * When the statistics for this service is already enabled, 950 * return the log and resets he statistics. 951 * When the statistics is not enabled, this method enable 952 * the statistics. Used for debugging purposes. 953 */ stats()954 public String stats() { 955 ICURWLock.Stats stats = factoryLock.resetStats(); 956 if (stats != null) { 957 return stats.toString(); 958 } 959 return "no stats"; 960 } 961 962 /** 963 * Return the name of this service. This will be the empty string if none was assigned. 964 */ getName()965 public String getName() { 966 return name; 967 } 968 969 /** 970 * Returns the result of super.toString, appending the name in curly braces. 971 */ 972 @Override toString()973 public String toString() { 974 return super.toString() + "{" + name + "}"; 975 } 976 } 977