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.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 implmentation 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 this.idcache = Collections.unmodifiableMap(mutableMap); 596 } finally { 597 factoryLock.releaseRead(); 598 } 599 } 600 } 601 return idcache; 602 } 603 private Map<String, Factory> idcache; 604 605 /** 606 * Convenience override for getDisplayName(String, ULocale) that 607 * uses the current default locale. 608 */ getDisplayName(String id)609 public String getDisplayName(String id) { 610 return getDisplayName(id, ULocale.getDefault(Category.DISPLAY)); 611 } 612 613 /** 614 * Given a visible id, return the display name in the requested locale. 615 * If there is no directly supported id corresponding to this id, return 616 * null. 617 */ getDisplayName(String id, ULocale locale)618 public String getDisplayName(String id, ULocale locale) { 619 Map<String, Factory> m = getVisibleIDMap(); 620 Factory f = m.get(id); 621 if (f != null) { 622 return f.getDisplayName(id, locale); 623 } 624 625 Key key = createKey(id); 626 while (key.fallback()) { 627 f = m.get(key.currentID()); 628 if (f != null) { 629 return f.getDisplayName(id, locale); 630 } 631 } 632 633 return null; 634 } 635 636 /** 637 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 638 * uses the current default Locale as the locale, null as 639 * the comparator, and null for the matchID. 640 */ getDisplayNames()641 public SortedMap<String, String> getDisplayNames() { 642 ULocale locale = ULocale.getDefault(Category.DISPLAY); 643 return getDisplayNames(locale, null, null); 644 } 645 646 /** 647 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 648 * uses null for the comparator, and null for the matchID. 649 */ getDisplayNames(ULocale locale)650 public SortedMap<String, String> getDisplayNames(ULocale locale) { 651 return getDisplayNames(locale, null, null); 652 } 653 654 /** 655 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 656 * uses null for the matchID, thus returning all display names. 657 */ getDisplayNames(ULocale locale, Comparator<Object> com)658 public SortedMap<String, String> getDisplayNames(ULocale locale, Comparator<Object> com) { 659 return getDisplayNames(locale, com, null); 660 } 661 662 /** 663 * Convenience override of getDisplayNames(ULocale, Comparator, String) that 664 * uses null for the comparator. 665 */ getDisplayNames(ULocale locale, String matchID)666 public SortedMap<String, String> getDisplayNames(ULocale locale, String matchID) { 667 return getDisplayNames(locale, null, matchID); 668 } 669 670 /** 671 * Return a snapshot of the mapping from display names to visible 672 * IDs for this service. This set will not change as factories 673 * are added or removed, but the supported ids will, so there is 674 * no guarantee that all and only the ids in the returned map will 675 * be visible and supported by the service in subsequent calls, 676 * nor is there any guarantee that the current display names match 677 * those in the set. The display names are sorted based on the 678 * comparator provided. 679 */ getDisplayNames(ULocale locale, Comparator<Object> com, String matchID)680 public SortedMap<String, String> getDisplayNames(ULocale locale, Comparator<Object> com, String matchID) { 681 SortedMap<String, String> dncache = null; 682 LocaleRef ref = dnref; 683 684 if (ref != null) { 685 dncache = ref.get(locale, com); 686 } 687 688 while (dncache == null) { 689 synchronized (this) { 690 if (ref == dnref || dnref == null) { 691 dncache = new TreeMap<String, String>(com); // sorted 692 693 Map<String, Factory> m = getVisibleIDMap(); 694 Iterator<Entry<String, Factory>> ei = m.entrySet().iterator(); 695 while (ei.hasNext()) { 696 Entry<String, Factory> e = ei.next(); 697 String id = e.getKey(); 698 Factory f = e.getValue(); 699 dncache.put(f.getDisplayName(id, locale), id); 700 } 701 702 dncache = Collections.unmodifiableSortedMap(dncache); 703 dnref = new LocaleRef(dncache, locale, com); 704 } else { 705 ref = dnref; 706 dncache = ref.get(locale, com); 707 } 708 } 709 } 710 711 Key matchKey = createKey(matchID); 712 if (matchKey == null) { 713 return dncache; 714 } 715 716 SortedMap<String, String> result = new TreeMap<String, String>(dncache); 717 Iterator<Entry<String, String>> iter = result.entrySet().iterator(); 718 while (iter.hasNext()) { 719 Entry<String, String> e = iter.next(); 720 if (!matchKey.isFallbackOf(e.getValue())) { 721 iter.remove(); 722 } 723 } 724 return result; 725 } 726 727 // we define a class so we get atomic simultaneous access to the 728 // locale, comparator, and corresponding map. 729 private static class LocaleRef { 730 private final ULocale locale; 731 private SortedMap<String, String> dnCache; 732 private Comparator<Object> com; 733 LocaleRef(SortedMap<String, String> dnCache, ULocale locale, Comparator<Object> com)734 LocaleRef(SortedMap<String, String> dnCache, ULocale locale, Comparator<Object> com) { 735 this.locale = locale; 736 this.com = com; 737 this.dnCache = dnCache; 738 } 739 740 get(ULocale loc, Comparator<Object> comp)741 SortedMap<String, String> get(ULocale loc, Comparator<Object> comp) { 742 SortedMap<String, String> m = dnCache; 743 if (m != null && 744 this.locale.equals(loc) && 745 (this.com == comp || (this.com != null && this.com.equals(comp)))) { 746 747 return m; 748 } 749 return null; 750 } 751 } 752 private LocaleRef dnref; 753 754 /** 755 * Return a snapshot of the currently registered factories. There 756 * is no guarantee that the list will still match the current 757 * factory list of the service subsequent to this call. 758 */ factories()759 public final List<Factory> factories() { 760 try { 761 factoryLock.acquireRead(); 762 return new ArrayList<Factory>(factories); 763 } 764 finally{ 765 factoryLock.releaseRead(); 766 } 767 } 768 769 /** 770 * A convenience override of registerObject(Object, String, boolean) 771 * that defaults visible to true. 772 */ registerObject(Object obj, String id)773 public Factory registerObject(Object obj, String id) { 774 return registerObject(obj, id, true); 775 } 776 777 /** 778 * Register an object with the provided id. The id will be 779 * canonicalized. The canonicalized ID will be returned by 780 * getVisibleIDs if visible is true. 781 */ registerObject(Object obj, String id, boolean visible)782 public Factory registerObject(Object obj, String id, boolean visible) { 783 String canonicalID = createKey(id).canonicalID(); 784 return registerFactory(new SimpleFactory(obj, canonicalID, visible)); 785 } 786 787 /** 788 * Register a Factory. Returns the factory if the service accepts 789 * the factory, otherwise returns null. The default implementation 790 * accepts all factories. 791 */ registerFactory(Factory factory)792 public final Factory registerFactory(Factory factory) { 793 if (factory == null) { 794 throw new NullPointerException(); 795 } 796 try { 797 factoryLock.acquireWrite(); 798 factories.add(0, factory); 799 clearCaches(); 800 } 801 finally { 802 factoryLock.releaseWrite(); 803 } 804 notifyChanged(); 805 return factory; 806 } 807 808 /** 809 * Unregister a factory. The first matching registered factory will 810 * be removed from the list. Returns true if a matching factory was 811 * removed. 812 */ unregisterFactory(Factory factory)813 public final boolean unregisterFactory(Factory factory) { 814 if (factory == null) { 815 throw new NullPointerException(); 816 } 817 818 boolean result = false; 819 try { 820 factoryLock.acquireWrite(); 821 if (factories.remove(factory)) { 822 result = true; 823 clearCaches(); 824 } 825 } 826 finally { 827 factoryLock.releaseWrite(); 828 } 829 830 if (result) { 831 notifyChanged(); 832 } 833 return result; 834 } 835 836 /** 837 * Reset the service to the default factories. The factory 838 * lock is acquired and then reInitializeFactories is called. 839 */ reset()840 public final void reset() { 841 try { 842 factoryLock.acquireWrite(); 843 reInitializeFactories(); 844 clearCaches(); 845 } 846 finally { 847 factoryLock.releaseWrite(); 848 } 849 notifyChanged(); 850 } 851 852 /** 853 * Reinitialize the factory list to its default state. By default 854 * this clears the list. Subclasses can override to provide other 855 * default initialization of the factory list. Subclasses must 856 * not call this method directly, as it must only be called while 857 * holding write access to the factory list. 858 */ reInitializeFactories()859 protected void reInitializeFactories() { 860 factories.clear(); 861 } 862 863 /** 864 * Return true if the service is in its default state. The default 865 * implementation returns true if there are no factories registered. 866 */ isDefault()867 public boolean isDefault() { 868 return factories.size() == defaultSize; 869 } 870 871 /** 872 * Set the default size to the current number of registered factories. 873 * Used by subclasses to customize the behavior of isDefault. 874 */ markDefault()875 protected void markDefault() { 876 defaultSize = factories.size(); 877 } 878 879 /** 880 * Create a key from an id. This creates a Key instance. 881 * Subclasses can override to define more useful keys appropriate 882 * to the factories they accept. If id is null, returns null. 883 */ createKey(String id)884 public Key createKey(String id) { 885 return id == null ? null : new Key(id); 886 } 887 888 /** 889 * Clear caches maintained by this service. Subclasses can 890 * override if they implement additional that need to be cleared 891 * when the service changes. Subclasses should generally not call 892 * this method directly, as it must only be called while 893 * synchronized on this. 894 */ clearCaches()895 protected void clearCaches() { 896 // we don't synchronize on these because methods that use them 897 // copy before use, and check for changes if they modify the 898 // caches. 899 cache = null; 900 idcache = null; 901 dnref = null; 902 } 903 904 /** 905 * Clears only the service cache. 906 * This can be called by subclasses when a change affects the service 907 * cache but not the id caches, e.g., when the default locale changes 908 * the resolution of ids changes, but not the visible ids themselves. 909 */ clearServiceCache()910 protected void clearServiceCache() { 911 cache = null; 912 } 913 914 /** 915 * ServiceListener is the listener that ICUService provides by default. 916 * ICUService will notifiy this listener when factories are added to 917 * or removed from the service. Subclasses can provide 918 * different listener interfaces that extend EventListener, and modify 919 * acceptsListener and notifyListener as appropriate. 920 */ 921 public static interface ServiceListener extends EventListener { serviceChanged(ICUService service)922 public void serviceChanged(ICUService service); 923 } 924 925 /** 926 * Return true if the listener is accepted; by default this 927 * requires a ServiceListener. Subclasses can override to accept 928 * different listeners. 929 */ 930 @Override acceptsListener(EventListener l)931 protected boolean acceptsListener(EventListener l) { 932 return l instanceof ServiceListener; 933 } 934 935 /** 936 * Notify the listener, which by default is a ServiceListener. 937 * Subclasses can override to use a different listener. 938 */ 939 @Override notifyListener(EventListener l)940 protected void notifyListener(EventListener l) { 941 ((ServiceListener)l).serviceChanged(this); 942 } 943 944 /** 945 * When the statistics for this service is already enabled, 946 * return the log and resets he statistics. 947 * When the statistics is not enabled, this method enable 948 * the statistics. Used for debugging purposes. 949 */ stats()950 public String stats() { 951 ICURWLock.Stats stats = factoryLock.resetStats(); 952 if (stats != null) { 953 return stats.toString(); 954 } 955 return "no stats"; 956 } 957 958 /** 959 * Return the name of this service. This will be the empty string if none was assigned. 960 */ getName()961 public String getName() { 962 return name; 963 } 964 965 /** 966 * Returns the result of super.toString, appending the name in curly braces. 967 */ 968 @Override toString()969 public String toString() { 970 return super.toString() + "{" + name + "}"; 971 } 972 } 973