2 *******************************************************************************
\r
3 * Copyright (C) 2001-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
7 package com.ibm.icu.impl;
\r
9 import java.lang.ref.SoftReference;
\r
10 import java.util.ArrayList;
\r
11 import java.util.Collections;
\r
12 import java.util.Comparator;
\r
13 import java.util.EventListener;
\r
14 import java.util.HashMap;
\r
15 import java.util.HashSet;
\r
16 import java.util.Iterator;
\r
17 import java.util.List;
\r
18 import java.util.ListIterator;
\r
19 import java.util.Map;
\r
20 import java.util.Set;
\r
21 import java.util.SortedMap;
\r
22 import java.util.TreeMap;
\r
23 import java.util.Map.Entry;
\r
25 import com.ibm.icu.util.ULocale;
\r
28 * <p>A Service provides access to service objects that implement a
\r
29 * particular service, e.g. transliterators. Users provide a String
\r
30 * id (for example, a locale string) to the service, and get back an
\r
31 * object for that id. Service objects can be any kind of object.
\r
32 * The service object is cached and returned for later queries, so
\r
33 * generally it should not be mutable, or the caller should clone the
\r
34 * object before modifying it.</p>
\r
36 * <p>Services 'canonicalize' the query id and use the canonical id to
\r
37 * query for the service. The service also defines a mechanism to
\r
38 * 'fallback' the id multiple times. Clients can optionally request
\r
39 * the actual id that was matched by a query when they use an id to
\r
40 * retrieve a service object.</p>
\r
42 * <p>Service objects are instantiated by Factory objects registered with
\r
43 * the service. The service queries each Factory in turn, from most recently
\r
44 * registered to earliest registered, until one returns a service object.
\r
45 * If none responds with a service object, a fallback id is generated,
\r
46 * and the process repeats until a service object is returned or until
\r
47 * the id has no further fallbacks.</p>
\r
49 * <p>Factories can be dynamically registered and unregistered with the
\r
50 * service. When registered, a Factory is installed at the head of
\r
51 * the factory list, and so gets 'first crack' at any keys or fallback
\r
52 * keys. When unregistered, it is removed from the service and can no
\r
53 * longer be located through it. Service objects generated by this
\r
54 * factory and held by the client are unaffected.</p>
\r
56 * <p>ICUService uses Keys to query factories and perform
\r
57 * fallback. The Key defines the canonical form of the id, and
\r
58 * implements the fallback strategy. Custom Keys can be defined that
\r
59 * parse complex IDs into components that Factories can more easily
\r
60 * use. The Key can cache the results of this parsing to save
\r
61 * repeated effort. ICUService provides convenience APIs that
\r
62 * take Strings and generate default Keys for use in querying.</p>
\r
64 * <p>ICUService provides API to get the list of ids publicly
\r
65 * supported by the service (although queries aren't restricted to
\r
66 * this list). This list contains only 'simple' IDs, and not fully
\r
67 * unique ids. Factories are associated with each simple ID and
\r
68 * the responsible factory can also return a human-readable localized
\r
69 * version of the simple ID, for use in user interfaces. ICUService
\r
70 * can also provide a sorted collection of the all the localized visible
\r
73 * <p>ICUService implements ICUNotifier, so that clients can register
\r
74 * to receive notification when factories are added or removed from
\r
75 * the service. ICUService provides a default EventListener subinterface,
\r
76 * ServiceListener, which can be registered with the service. When
\r
77 * the service changes, the ServiceListener's serviceChanged method
\r
78 * is called, with the service as the only argument.</p>
\r
80 * <p>The ICUService API is both rich and generic, and it is expected
\r
81 * that most implementations will statically 'wrap' ICUService to
\r
82 * present a more appropriate API-- for example, to declare the type
\r
83 * of the objects returned from get, to limit the factories that can
\r
84 * be registered with the service, or to define their own listener
\r
85 * interface with a custom callback method. They might also customize
\r
86 * ICUService by overriding it, for example, to customize the Key and
\r
87 * fallback strategy. ICULocaleService is a customized service that
\r
88 * uses Locale names as ids and uses Keys that implement the standard
\r
89 * resource bundle fallback strategy.<p>
\r
91 public class ICUService extends ICUNotifier {
\r
93 * Name used for debugging.
\r
95 protected final String name;
\r
100 public ICUService() {
\r
104 private static final boolean DEBUG = ICUDebug.enabled("service");
\r
106 * Construct with a name (useful for debugging).
\r
108 public ICUService(String name) {
\r
113 * Access to factories is protected by a read-write lock. This is
\r
114 * to allow multiple threads to read concurrently, but keep
\r
115 * changes to the factory list atomic with respect to all readers.
\r
117 private final ICURWLock factoryLock = new ICURWLock();
\r
120 * All the factories registered with this service.
\r
122 private final List<Factory> factories = new ArrayList<Factory>();
\r
125 * Record the default number of factories for this service.
\r
126 * Can be set by markDefault.
\r
128 private int defaultSize = 0;
\r
131 * Keys are used to communicate with factories to generate an
\r
132 * instance of the service. Keys define how ids are
\r
133 * canonicalized, provide both a current id and a current
\r
134 * descriptor to use in querying the cache and factories, and
\r
135 * determine the fallback strategy.</p>
\r
137 * <p>Keys provide both a currentDescriptor and a currentID.
\r
138 * The descriptor contains an optional prefix, followed by '/'
\r
139 * and the currentID. Factories that handle complex keys,
\r
140 * for example number format factories that generate multiple
\r
141 * kinds of formatters for the same locale, use the descriptor
\r
142 * to provide a fully unique identifier for the service object,
\r
143 * while using the currentID (in this case, the locale string),
\r
144 * as the visible IDs that can be localized.
\r
146 * <p> The default implementation of Key has no fallbacks and
\r
147 * has no custom descriptors.</p>
\r
149 public static class Key {
\r
150 private final String id;
\r
153 * Construct a key from an id.
\r
155 public Key(String id) {
\r
160 * Return the original ID used to construct this key.
\r
162 public final String id() {
\r
167 * Return the canonical version of the original ID. This implementation
\r
168 * returns the original ID unchanged.
\r
170 public String canonicalID() {
\r
175 * Return the (canonical) current ID. This implementation
\r
176 * returns the canonical ID.
\r
178 public String currentID() {
\r
179 return canonicalID();
\r
183 * Return the current descriptor. This implementation returns
\r
184 * the current ID. The current descriptor is used to fully
\r
185 * identify an instance of the service in the cache. A
\r
186 * factory may handle all descriptors for an ID, or just a
\r
187 * particular descriptor. The factory can either parse the
\r
188 * descriptor or use custom API on the key in order to
\r
189 * instantiate the service.
\r
191 public String currentDescriptor() {
\r
192 return "/" + currentID();
\r
196 * If the key has a fallback, modify the key and return true,
\r
197 * otherwise return false. The current ID will change if there
\r
198 * is a fallback. No currentIDs should be repeated, and fallback
\r
199 * must eventually return false. This implmentation has no fallbacks
\r
200 * and always returns false.
\r
202 public boolean fallback() {
\r
207 * If a key created from id would eventually fallback to match the
\r
208 * canonical ID of this key, return true.
\r
210 public boolean isFallbackOf(String idToCheck) {
\r
211 return canonicalID().equals(idToCheck);
\r
216 * Factories generate the service objects maintained by the
\r
217 * service. A factory generates a service object from a key,
\r
218 * updates id->factory mappings, and returns the display name for
\r
221 public static interface Factory {
\r
224 * Create a service object from the key, if this factory
\r
225 * supports the key. Otherwise, return null.
\r
227 * <p>If the factory supports the key, then it can call
\r
228 * the service's getKey(Key, String[], Factory) method
\r
229 * passing itself as the factory to get the object that
\r
230 * the service would have created prior to the factory's
\r
231 * registration with the service. This can change the
\r
232 * key, so any information required from the key should
\r
233 * be extracted before making such a callback.
\r
235 public Object create(Key key, ICUService service);
\r
238 * Update the result IDs (not descriptors) to reflect the IDs
\r
239 * this factory handles. This function and getDisplayName are
\r
240 * used to support ICUService.getDisplayNames. Basically, the
\r
241 * factory has to determine which IDs it will permit to be
\r
242 * available, and of those, which it will provide localized
\r
243 * display names for. In most cases this reflects the IDs that
\r
244 * the factory directly supports.
\r
246 public void updateVisibleIDs(Map<String, Factory> result);
\r
249 * Return the display name for this id in the provided locale.
\r
250 * This is an localized id, not a descriptor. If the id is
\r
251 * not visible or not defined by the factory, return null.
\r
252 * If locale is null, return id unchanged.
\r
254 public String getDisplayName(String id, ULocale locale);
\r
258 * A default implementation of factory. This provides default
\r
259 * implementations for subclasses, and implements a singleton
\r
260 * factory that matches a single id and returns a single
\r
261 * (possibly deferred-initialized) instance. This implements
\r
262 * updateVisibleIDs to add a mapping from its ID to itself
\r
263 * if visible is true, or to remove any existing mapping
\r
264 * for its ID if visible is false.
\r
266 public static class SimpleFactory implements Factory {
\r
267 protected Object instance;
\r
268 protected String id;
\r
269 protected boolean visible;
\r
272 * Convenience constructor that calls SimpleFactory(Object, String, boolean)
\r
273 * with visible true.
\r
275 public SimpleFactory(Object instance, String id) {
\r
276 this(instance, id, true);
\r
280 * Construct a simple factory that maps a single id to a single
\r
281 * service instance. If visible is true, the id will be visible.
\r
282 * Neither the instance nor the id can be null.
\r
284 public SimpleFactory(Object instance, String id, boolean visible) {
\r
285 if (instance == null || id == null) {
\r
286 throw new IllegalArgumentException("Instance or id is null");
\r
288 this.instance = instance;
\r
290 this.visible = visible;
\r
294 * Return the service instance if the factory's id is equal to
\r
295 * the key's currentID. Service is ignored.
\r
297 public Object create(Key key, ICUService service) {
\r
298 if (id.equals(key.currentID())) {
\r
305 * If visible, adds a mapping from id -> this to the result,
\r
306 * otherwise removes id from result.
\r
308 public void updateVisibleIDs(Map<String, Factory> result) {
\r
310 result.put(id, this);
\r
317 * If this.id equals id, returns id regardless of locale,
\r
318 * otherwise returns null. (This default implementation has
\r
319 * no localized id information.)
\r
321 public String getDisplayName(String identifier, ULocale locale) {
\r
322 return (visible && id.equals(identifier)) ? identifier : null;
\r
328 public String toString() {
\r
329 StringBuilder buf = new StringBuilder(super.toString());
\r
330 buf.append(", id: ");
\r
332 buf.append(", visible: ");
\r
333 buf.append(visible);
\r
334 return buf.toString();
\r
339 * Convenience override for get(String, String[]). This uses
\r
340 * createKey to create a key for the provided descriptor.
\r
342 public Object get(String descriptor) {
\r
343 return getKey(createKey(descriptor), null);
\r
347 * Convenience override for get(Key, String[]). This uses
\r
348 * createKey to create a key from the provided descriptor.
\r
350 public Object get(String descriptor, String[] actualReturn) {
\r
351 if (descriptor == null) {
\r
352 throw new NullPointerException("descriptor must not be null");
\r
354 return getKey(createKey(descriptor), actualReturn);
\r
358 * Convenience override for get(Key, String[]).
\r
360 public Object getKey(Key key) {
\r
361 return getKey(key, null);
\r
365 * <p>Given a key, return a service object, and, if actualReturn
\r
366 * is not null, the descriptor with which it was found in the
\r
367 * first element of actualReturn. If no service object matches
\r
368 * this key, return null, and leave actualReturn unchanged.</p>
\r
370 * <p>This queries the cache using the key's descriptor, and if no
\r
371 * object in the cache matches it, tries the key on each
\r
372 * registered factory, in order. If none generates a service
\r
373 * object for the key, repeats the process with each fallback of
\r
374 * the key, until either one returns a service object, or the key
\r
375 * has no fallback.</p>
\r
377 * <p>If key is null, just returns null.</p>
\r
379 public Object getKey(Key key, String[] actualReturn) {
\r
380 return getKey(key, actualReturn, null);
\r
386 public Object getKey(Key key, String[] actualReturn, Factory factory) {
\r
387 if (factories.size() == 0) {
\r
388 return handleDefault(key, actualReturn);
\r
391 if (DEBUG) System.out.println("Service: " + name + " key: " + key.canonicalID());
\r
393 CacheEntry result = null;
\r
396 // The factory list can't be modified until we're done,
\r
397 // otherwise we might update the cache with an invalid result.
\r
398 // The cache has to stay in synch with the factory list.
\r
399 factoryLock.acquireRead();
\r
401 Map<String, CacheEntry> cache = null;
\r
402 SoftReference<Map<String, CacheEntry>> cref = cacheref; // copy so we don't need to sync on this
\r
403 if (cref != null) {
\r
404 if (DEBUG) System.out.println("Service " + name + " ref exists");
\r
405 cache = cref.get();
\r
407 if (cache == null) {
\r
408 if (DEBUG) System.out.println("Service " + name + " cache was empty");
\r
409 // synchronized since additions and queries on the cache must be atomic
\r
410 // they can be interleaved, though
\r
411 cache = Collections.synchronizedMap(new HashMap<String, CacheEntry>());
\r
412 // hardRef = cache; // debug
\r
413 cref = new SoftReference<Map<String, CacheEntry>>(cache);
\r
416 String currentDescriptor = null;
\r
417 ArrayList<String> cacheDescriptorList = null;
\r
418 boolean putInCache = false;
\r
422 int startIndex = 0;
\r
423 int limit = factories.size();
\r
424 boolean cacheResult = true;
\r
425 if (factory != null) {
\r
426 for (int i = 0; i < limit; ++i) {
\r
427 if (factory == factories.get(i)) {
\r
428 startIndex = i + 1;
\r
432 if (startIndex == 0) {
\r
433 throw new IllegalStateException("Factory " + factory + "not registered with service: " + this);
\r
435 cacheResult = false;
\r
440 currentDescriptor = key.currentDescriptor();
\r
441 if (DEBUG) System.out.println(name + "[" + NDebug++ + "] looking for: " + currentDescriptor);
\r
442 result = cache.get(currentDescriptor);
\r
443 if (result != null) {
\r
444 if (DEBUG) System.out.println(name + " found with descriptor: " + currentDescriptor);
\r
447 if (DEBUG) System.out.println("did not find: " + currentDescriptor + " in cache");
\r
450 // first test of cache failed, so we'll have to update
\r
451 // the cache if we eventually succeed-- that is, if we're
\r
452 // going to update the cache at all.
\r
453 putInCache = cacheResult;
\r
456 int index = startIndex;
\r
457 while (index < limit) {
\r
458 Factory f = factories.get(index++);
\r
459 if (DEBUG) System.out.println("trying factory[" + (index-1) + "] " + f.toString());
\r
460 Object service = f.create(key, this);
\r
461 if (service != null) {
\r
462 result = new CacheEntry(currentDescriptor, service);
\r
463 if (DEBUG) System.out.println(name + " factory supported: " + currentDescriptor + ", caching");
\r
466 if (DEBUG) System.out.println("factory did not support: " + currentDescriptor);
\r
470 // prepare to load the cache with all additional ids that
\r
471 // will resolve to result, assuming we'll succeed. We
\r
472 // don't want to keep querying on an id that's going to
\r
473 // fallback to the one that succeeded, we want to hit the
\r
474 // cache the first time next goaround.
\r
475 if (cacheDescriptorList == null) {
\r
476 cacheDescriptorList = new ArrayList<String>(5);
\r
478 cacheDescriptorList.add(currentDescriptor);
\r
480 } while (key.fallback());
\r
482 if (result != null) {
\r
484 if (DEBUG) System.out.println("caching '" + result.actualDescriptor + "'");
\r
485 cache.put(result.actualDescriptor, result);
\r
486 if (cacheDescriptorList != null) {
\r
487 for (String desc : cacheDescriptorList) {
\r
488 if (DEBUG) System.out.println(name + " adding descriptor: '" + desc + "' for actual: '" + result.actualDescriptor + "'");
\r
490 cache.put(desc, result);
\r
493 // Atomic update. We held the read lock all this time
\r
494 // so we know our cache is consistent with the factory list.
\r
495 // We might stomp over a cache that some other thread
\r
496 // rebuilt, but that's the breaks. They're both good.
\r
500 if (actualReturn != null) {
\r
501 // strip null prefix
\r
502 if (result.actualDescriptor.indexOf("/") == 0) {
\r
503 actualReturn[0] = result.actualDescriptor.substring(1);
\r
505 actualReturn[0] = result.actualDescriptor;
\r
509 if (DEBUG) System.out.println("found in service: " + name);
\r
511 return result.service;
\r
515 factoryLock.releaseRead();
\r
519 if (DEBUG) System.out.println("not found in service: " + name);
\r
521 return handleDefault(key, actualReturn);
\r
523 private SoftReference<Map<String, CacheEntry>> cacheref;
\r
525 // Record the actual id for this service in the cache, so we can return it
\r
526 // even if we succeed later with a different id.
\r
527 private static final class CacheEntry {
\r
528 final String actualDescriptor;
\r
529 final Object service;
\r
530 CacheEntry(String actualDescriptor, Object service) {
\r
531 this.actualDescriptor = actualDescriptor;
\r
532 this.service = service;
\r
538 * Default handler for this service if no factory in the list
\r
541 protected Object handleDefault(Key key, String[] actualIDReturn) {
\r
546 * Convenience override for getVisibleIDs(String) that passes null
\r
547 * as the fallback, thus returning all visible IDs.
\r
549 public Set<String> getVisibleIDs() {
\r
550 return getVisibleIDs(null);
\r
554 * <p>Return a snapshot of the visible IDs for this service. This
\r
555 * set will not change as Factories are added or removed, but the
\r
556 * supported ids will, so there is no guarantee that all and only
\r
557 * the ids in the returned set are visible and supported by the
\r
558 * service in subsequent calls.</p>
\r
560 * <p>matchID is passed to createKey to create a key. If the
\r
561 * key is not null, it is used to filter out ids that don't have
\r
562 * the key as a fallback.
\r
564 public Set<String> getVisibleIDs(String matchID) {
\r
565 Set<String> result = getVisibleIDMap().keySet();
\r
567 Key fallbackKey = createKey(matchID);
\r
569 if (fallbackKey != null) {
\r
570 Set<String> temp = new HashSet<String>(result.size());
\r
571 for (String id : result) {
\r
572 if (fallbackKey.isFallbackOf(id)) {
\r
582 * Return a map from visible ids to factories.
\r
584 private Map<String, Factory> getVisibleIDMap() {
\r
585 Map<String, Factory> idcache = null;
\r
586 SoftReference<Map<String, Factory>> ref = idref;
\r
588 idcache = ref.get();
\r
590 while (idcache == null) {
\r
591 synchronized (this) { // or idref-only lock?
\r
592 if (ref == idref || idref == null) {
\r
593 // no other thread updated idref before we got the lock, so
\r
594 // grab the factory list and update it ourselves
\r
596 factoryLock.acquireRead();
\r
597 idcache = new HashMap<String, Factory>();
\r
598 ListIterator<Factory> lIter = factories.listIterator(factories.size());
\r
599 while (lIter.hasPrevious()) {
\r
600 Factory f = lIter.previous();
\r
601 f.updateVisibleIDs(idcache);
\r
603 idcache = Collections.unmodifiableMap(idcache);
\r
604 idref = new SoftReference<Map<String, Factory>>(idcache);
\r
607 factoryLock.releaseRead();
\r
610 // another thread updated idref, but gc may have stepped
\r
611 // in and undone its work, leaving idcache null. If so,
\r
614 idcache = ref.get();
\r
621 private SoftReference<Map<String, Factory>> idref;
\r
624 * Convenience override for getDisplayName(String, ULocale) that
\r
625 * uses the current default locale.
\r
627 public String getDisplayName(String id) {
\r
628 return getDisplayName(id, ULocale.getDefault());
\r
632 * Given a visible id, return the display name in the requested locale.
\r
633 * If there is no directly supported id corresponding to this id, return
\r
636 public String getDisplayName(String id, ULocale locale) {
\r
637 Map<String, Factory> m = getVisibleIDMap();
\r
638 Factory f = m.get(id);
\r
640 return f.getDisplayName(id, locale);
\r
643 Key key = createKey(id);
\r
644 while (key.fallback()) {
\r
645 f = m.get(key.currentID());
\r
647 return f.getDisplayName(id, locale);
\r
655 * Convenience override of getDisplayNames(ULocale, Comparator, String) that
\r
656 * uses the current default Locale as the locale, null as
\r
657 * the comparator, and null for the matchID.
\r
659 public SortedMap<String, String> getDisplayNames() {
\r
660 ULocale locale = ULocale.getDefault();
\r
661 return getDisplayNames(locale, null, null);
\r
665 * Convenience override of getDisplayNames(ULocale, Comparator, String) that
\r
666 * uses null for the comparator, and null for the matchID.
\r
668 public SortedMap<String, String> getDisplayNames(ULocale locale) {
\r
669 return getDisplayNames(locale, null, null);
\r
673 * Convenience override of getDisplayNames(ULocale, Comparator, String) that
\r
674 * uses null for the matchID, thus returning all display names.
\r
676 public SortedMap<String, String> getDisplayNames(ULocale locale, Comparator<Object> com) {
\r
677 return getDisplayNames(locale, com, null);
\r
681 * Convenience override of getDisplayNames(ULocale, Comparator, String) that
\r
682 * uses null for the comparator.
\r
684 public SortedMap<String, String> getDisplayNames(ULocale locale, String matchID) {
\r
685 return getDisplayNames(locale, null, matchID);
\r
689 * Return a snapshot of the mapping from display names to visible
\r
690 * IDs for this service. This set will not change as factories
\r
691 * are added or removed, but the supported ids will, so there is
\r
692 * no guarantee that all and only the ids in the returned map will
\r
693 * be visible and supported by the service in subsequent calls,
\r
694 * nor is there any guarantee that the current display names match
\r
695 * those in the set. The display names are sorted based on the
\r
696 * comparator provided.
\r
698 public SortedMap<String, String> getDisplayNames(ULocale locale, Comparator<Object> com, String matchID) {
\r
699 SortedMap<String, String> dncache = null;
\r
700 LocaleRef ref = dnref;
\r
703 dncache = ref.get(locale, com);
\r
706 while (dncache == null) {
\r
707 synchronized (this) {
\r
708 if (ref == dnref || dnref == null) {
\r
709 dncache = new TreeMap<String, String>(com); // sorted
\r
711 Map<String, Factory> m = getVisibleIDMap();
\r
712 Iterator<Entry<String, Factory>> ei = m.entrySet().iterator();
\r
713 while (ei.hasNext()) {
\r
714 Entry<String, Factory> e = ei.next();
\r
715 String id = e.getKey();
\r
716 Factory f = e.getValue();
\r
717 dncache.put(f.getDisplayName(id, locale), id);
\r
720 dncache = Collections.unmodifiableSortedMap(dncache);
\r
721 dnref = new LocaleRef(dncache, locale, com);
\r
724 dncache = ref.get(locale, com);
\r
729 Key matchKey = createKey(matchID);
\r
730 if (matchKey == null) {
\r
734 SortedMap<String, String> result = new TreeMap<String, String>(dncache);
\r
735 Iterator<Entry<String, String>> iter = result.entrySet().iterator();
\r
736 while (iter.hasNext()) {
\r
737 Entry<String, String> e = iter.next();
\r
738 if (!matchKey.isFallbackOf(e.getValue())) {
\r
745 // we define a class so we get atomic simultaneous access to the
\r
746 // locale, comparator, and corresponding map.
\r
747 private static class LocaleRef {
\r
748 private final ULocale locale;
\r
749 private SoftReference<SortedMap<String, String>> ref;
\r
750 private Comparator<Object> com;
\r
752 LocaleRef(SortedMap<String, String> dnCache, ULocale locale, Comparator<Object> com) {
\r
753 this.locale = locale;
\r
755 this.ref = new SoftReference<SortedMap<String, String>>(dnCache);
\r
759 SortedMap<String, String> get(ULocale loc, Comparator<Object> comp) {
\r
760 SortedMap<String, String> m = ref.get();
\r
762 this.locale.equals(loc) &&
\r
763 (this.com == comp || (this.com != null && this.com.equals(comp)))) {
\r
770 private LocaleRef dnref;
\r
773 * Return a snapshot of the currently registered factories. There
\r
774 * is no guarantee that the list will still match the current
\r
775 * factory list of the service subsequent to this call.
\r
777 public final List<Factory> factories() {
\r
779 factoryLock.acquireRead();
\r
780 return new ArrayList<Factory>(factories);
\r
783 factoryLock.releaseRead();
\r
788 * A convenience override of registerObject(Object, String, boolean)
\r
789 * that defaults visible to true.
\r
791 public Factory registerObject(Object obj, String id) {
\r
792 return registerObject(obj, id, true);
\r
796 * Register an object with the provided id. The id will be
\r
797 * canonicalized. The canonicalized ID will be returned by
\r
798 * getVisibleIDs if visible is true.
\r
800 public Factory registerObject(Object obj, String id, boolean visible) {
\r
801 String canonicalID = createKey(id).canonicalID();
\r
802 return registerFactory(new SimpleFactory(obj, canonicalID, visible));
\r
806 * Register a Factory. Returns the factory if the service accepts
\r
807 * the factory, otherwise returns null. The default implementation
\r
808 * accepts all factories.
\r
810 public final Factory registerFactory(Factory factory) {
\r
811 if (factory == null) {
\r
812 throw new NullPointerException();
\r
815 factoryLock.acquireWrite();
\r
816 factories.add(0, factory);
\r
820 factoryLock.releaseWrite();
\r
827 * Unregister a factory. The first matching registered factory will
\r
828 * be removed from the list. Returns true if a matching factory was
\r
831 public final boolean unregisterFactory(Factory factory) {
\r
832 if (factory == null) {
\r
833 throw new NullPointerException();
\r
836 boolean result = false;
\r
838 factoryLock.acquireWrite();
\r
839 if (factories.remove(factory)) {
\r
845 factoryLock.releaseWrite();
\r
855 * Reset the service to the default factories. The factory
\r
856 * lock is acquired and then reInitializeFactories is called.
\r
858 public final void reset() {
\r
860 factoryLock.acquireWrite();
\r
861 reInitializeFactories();
\r
865 factoryLock.releaseWrite();
\r
871 * Reinitialize the factory list to its default state. By default
\r
872 * this clears the list. Subclasses can override to provide other
\r
873 * default initialization of the factory list. Subclasses must
\r
874 * not call this method directly, as it must only be called while
\r
875 * holding write access to the factory list.
\r
877 protected void reInitializeFactories() {
\r
882 * Return true if the service is in its default state. The default
\r
883 * implementation returns true if there are no factories registered.
\r
885 public boolean isDefault() {
\r
886 return factories.size() == defaultSize;
\r
890 * Set the default size to the current number of registered factories.
\r
891 * Used by subclasses to customize the behavior of isDefault.
\r
893 protected void markDefault() {
\r
894 defaultSize = factories.size();
\r
898 * Create a key from an id. This creates a Key instance.
\r
899 * Subclasses can override to define more useful keys appropriate
\r
900 * to the factories they accept. If id is null, returns null.
\r
902 public Key createKey(String id) {
\r
903 return id == null ? null : new Key(id);
\r
907 * Clear caches maintained by this service. Subclasses can
\r
908 * override if they implement additional that need to be cleared
\r
909 * when the service changes. Subclasses should generally not call
\r
910 * this method directly, as it must only be called while
\r
911 * synchronized on this.
\r
913 protected void clearCaches() {
\r
914 // we don't synchronize on these because methods that use them
\r
915 // copy before use, and check for changes if they modify the
\r
923 * Clears only the service cache.
\r
924 * This can be called by subclasses when a change affects the service
\r
925 * cache but not the id caches, e.g., when the default locale changes
\r
926 * the resolution of ids changes, but not the visible ids themselves.
\r
928 protected void clearServiceCache() {
\r
933 * ServiceListener is the listener that ICUService provides by default.
\r
934 * ICUService will notifiy this listener when factories are added to
\r
935 * or removed from the service. Subclasses can provide
\r
936 * different listener interfaces that extend EventListener, and modify
\r
937 * acceptsListener and notifyListener as appropriate.
\r
939 public static interface ServiceListener extends EventListener {
\r
940 public void serviceChanged(ICUService service);
\r
944 * Return true if the listener is accepted; by default this
\r
945 * requires a ServiceListener. Subclasses can override to accept
\r
946 * different listeners.
\r
948 protected boolean acceptsListener(EventListener l) {
\r
949 return l instanceof ServiceListener;
\r
953 * Notify the listener, which by default is a ServiceListener.
\r
954 * Subclasses can override to use a different listener.
\r
956 protected void notifyListener(EventListener l) {
\r
957 ((ServiceListener)l).serviceChanged(this);
\r
961 * Return a string describing the statistics for this service.
\r
962 * This also resets the statistics. Used for debugging purposes.
\r
964 public String stats() {
\r
965 ICURWLock.Stats stats = factoryLock.resetStats();
\r
966 if (stats != null) {
\r
967 return stats.toString();
\r
973 * Return the name of this service. This will be the empty string if none was assigned.
\r
975 public String getName() {
\r
980 * Returns the result of super.toString, appending the name in curly braces.
\r
982 public String toString() {
\r
983 return super.toString() + "{" + name + "}";
\r