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