001/* ServiceRegistry.java -- A simple registry for service providers.
002   Copyright (C) 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.imageio.spi;
040
041import gnu.classpath.ServiceFactory;
042
043import java.util.ArrayList;
044import java.util.Collection;
045import java.util.Collections;
046import java.util.Comparator;
047import java.util.HashSet;
048import java.util.IdentityHashMap;
049import java.util.Iterator;
050import java.util.LinkedList;
051import java.util.Map;
052import java.util.NoSuchElementException;
053import java.util.Set;
054
055/**
056 * A registry for service providers.
057 *
058 * @since 1.4
059 *
060 * @author Michael Koch (konqueror@gmx.de)
061 * @author Sascha Brawer (brawer@dandelis.ch)
062 */
063public class ServiceRegistry
064{
065  // Package-private to avoid a trampoline.
066  /**
067   * The service categories of this registry.
068   *
069   * <p>Note that we expect that only very few categories will
070   * typically be used with a registry. The most common case will be
071   * one, it seems unlikely that any registry would contain more than
072   * five or six categories. Therefore, we intentionally avoid the
073   * overhead of a HashMap.
074   *
075   * @see #providers
076   */
077  final Class[] categories;
078
079
080  /**
081   * The registered providers for each service category, indexed by
082   * the same index as the {@link #categories} array. If no provider
083   * is registered for a category, the array entry will be
084   * <code>null</code>.
085   *
086   * <p>Note that we expect that only very few providers will
087   * typically be registered for a category. The most common case will
088   * be one or two. Therefore, we intentionally avoid the overhead of
089   * a HashMap.
090   */
091  private final LinkedList[] providers;
092
093
094  /**
095   * The ordring constaints for each service category, indexed by the
096   * same index as the {@link #categories} array. The constraints for
097   * a service category are stored as a <code>Map&lt;Object,
098   * Set&lt;Object&gt;&gt;</code>, where the Map&#x2019;s values are
099   * those providers that need to come after the key.  If no
100   * constraints are imposed on the providers of a category, the array
101   * entry will be <code>null</code>. If no constraints have been set
102   * whatsoever, <code>constraints</code> will be <code>null</code>.
103   *
104   * <p>Note that we expect that only very few constraints will
105   * typically be imposed on a category. The most common case will
106   * be zero.
107   */
108  private IdentityHashMap[] constraints;
109
110
111  /**
112   * Constructs a <code>ServiceRegistry</code> for the specified
113   * service categories.
114   *
115   * @param categories the categories to support
116   *
117   * @throws IllegalArgumentException if <code>categories</code> is
118   * <code>null</code>, or if its {@link Iterator#next()} method
119   * returns <code>null</code>.
120   *
121   * @throws ClassCastException if <code>categories</code> does not
122   * iterate over instances of {@link java.lang.Class}.
123   */
124  public ServiceRegistry(Iterator<Class<?>> categories)
125  {
126    ArrayList cats = new ArrayList(/* expected size */ 10);
127
128    if (categories == null)
129      throw new IllegalArgumentException();
130
131    while (categories.hasNext())
132      {
133        Class cat = (Class) categories.next();
134        if (cat == null)
135          throw new IllegalArgumentException();
136        cats.add(cat);
137      }
138
139    int numCats = cats.size();
140    this.categories = (Class[]) cats.toArray(new Class[numCats]);
141    this.providers = new LinkedList[numCats];
142  }
143
144
145  /**
146   * Finds service providers that are implementing the specified
147   * Service Provider Interface.
148   *
149   * <p><b>On-demand loading:</b> Loading and initializing service
150   * providers is delayed as much as possible. The rationale is that
151   * typical clients will iterate through the set of installed service
152   * providers until one is found that matches some criteria (like
153   * supported formats, or quality of service). In such scenarios, it
154   * might make sense to install only the frequently needed service
155   * providers on the local machine. More exotic providers can be put
156   * onto a server; the server will only be contacted when no suitable
157   * service could be found locally.</p>
158   *
159   * <p><b>Security considerations:</b> Any loaded service providers
160   * are loaded through the specified ClassLoader, or the system
161   * ClassLoader if <code>classLoader</code> is
162   * <code>null</code>. When <code>lookupProviders</code> is called,
163   * the current {@link java.security.AccessControlContext} gets
164   * recorded. This captured security context will determine the
165   * permissions when services get loaded via the <code>next()</code>
166   * method of the returned <code>Iterator</code>.</p>
167   *
168   * @param spi the service provider interface which must be
169   * implemented by any loaded service providers.
170   *
171   * @param loader the class loader that will be used to load the
172   * service providers, or <code>null</code> for the system class
173   * loader. For using the context class loader, see {@link
174   * #lookupProviders(Class)}.
175   *
176   * @return an iterator over instances of <code>spi</code>.
177   *
178   * @throws IllegalArgumentException if <code>spi</code> is
179   * <code>null</code>.
180   */
181  public static <T> Iterator<T> lookupProviders(Class<T> spi,
182                                                ClassLoader loader)
183  {
184    return ServiceFactory.lookupProviders(spi, loader);
185  }
186
187
188  /**
189   * Finds service providers that are implementing the specified
190   * Service Provider Interface, using the context class loader
191   * for loading providers.
192   *
193   * @param spi the service provider interface which must be
194   * implemented by any loaded service providers.
195   *
196   * @return an iterator over instances of <code>spi</code>.
197   *
198   * @throws IllegalArgumentException if <code>spi</code> is
199   * <code>null</code>.
200   *
201   * @see #lookupProviders(Class, ClassLoader)
202   */
203  public static <T> Iterator<T> lookupProviders(Class<T> spi)
204  {
205    return ServiceFactory.lookupProviders(spi);
206  }
207
208
209  /**
210   * Returns an iterator over all service categories.
211   *
212   * @return an unmodifiable {@link
213   * java.util.Iterator}&lt;{@link java.lang.Class}&gt;.
214   */
215  public Iterator<Class<?>> getCategories()
216  {
217    return new Iterator()
218      {
219        int index = -1;
220
221        public boolean hasNext()
222        {
223          return index < categories.length - 1;
224        }
225
226        public Object next()
227        {
228          if (!hasNext())
229            throw new NoSuchElementException();
230
231          return categories[++index];
232        }
233
234        public void remove()
235        {
236          throw new UnsupportedOperationException();
237        }
238      };
239  }
240
241
242  /**
243   * Registers a provider for a service category which is specified by
244   * the class-internal category ID.
245   *
246   * @param provider the service provider to be registered.
247   *
248   * @param cat the service category, which is identified by an index
249   * into the {@link #categories} array.
250   *
251   * @return <code>true</code> if <code>provider</code> is the first
252   * provider that gets registered for the specified service category;
253   * <code>false</code> if other providers have already been
254   * registered for the same servide category.
255   *
256   * @throws IllegalArgumentException if <code>provider</code> is
257   * <code>null</code>.
258   *
259   * @throws ClassCastException if <code>provider</code> does not
260   * implement the specified service provider interface.
261   */
262  private synchronized boolean registerServiceProvider(Object provider,
263                                                       int cat)
264  {
265    LinkedList provs;
266    boolean result;
267    Class category;
268
269    if (provider == null)
270      throw new IllegalArgumentException();
271
272    category = categories[cat];
273    if (!category.isInstance(provider))
274      throw new ClassCastException(category.getName());
275
276    provs = providers[cat];
277    if (provs == null)
278    {
279      result = true;
280      provs = providers[cat] = new LinkedList();
281    }
282    else
283      result = false;
284
285    provs.add(provider);
286    if (provider instanceof RegisterableService)
287      ((RegisterableService) provider).onRegistration(this, category);
288
289    return result;
290  }
291
292
293  /**
294   * Registers a provider for the specified service category.
295   *
296   * <p>If <code>provider</code> implements the {@link
297   * RegisterableService} interface, its {@link
298   * RegisterableService#onRegistration onRegistration} method is
299   * invoked in order to inform the provider about the addition to
300   * this registry.
301   *
302   * @param provider the service provider to be registered.
303   *
304   * @param category the service category under which
305   * <code>provider</code> shall be registered.
306   *
307   * @return <code>true</code> if <code>provider</code> is the first
308   * provider that gets registered for the specified service category;
309   * <code>false</code> if other providers have already been
310   * registered for the same servide category.
311   *
312   * @throws IllegalArgumentException if <code>provider</code> is
313   * <code>null</code>, or if <code>category</code> is not among the
314   * categories passed to the {@linkplain #ServiceRegistry(Iterator)
315   * constructor} of this ServiceRegistry.
316   *
317   * @throws ClassCastException if <code>provider</code> does not
318   * implement <code>category</code>.
319   */
320  public synchronized <T> boolean registerServiceProvider(T provider,
321                                                          Class<T> category)
322  {
323    for (int i = 0; i < categories.length; i++)
324      if (categories[i] == category)
325        return registerServiceProvider(provider, i);
326    throw new IllegalArgumentException();
327  }
328
329
330  /**
331   * Registers a provider under all service categories it
332   * implements.
333   *
334   * <p>If <code>provider</code> implements the {@link
335   * RegisterableService} interface, its {@link
336   * RegisterableService#onRegistration onRegistration} method is
337   * invoked in order to inform the provider about the addition to
338   * this registry. If <code>provider</code> implements several
339   * service categories, <code>onRegistration</code> gets called
340   * multiple times.
341   *
342   * @param provider the service provider to be registered.
343   *
344   * @throws IllegalArgumentException if <code>provider</code> is
345   * <code>null</code>, or if <code>provider</code> does not implement
346   * any of the service categories passed to the {@linkplain
347   * #ServiceRegistry(Iterator) constructor} of this ServiceRegistry.
348   */
349  public synchronized void registerServiceProvider(Object provider)
350  {
351    boolean ok = false;
352
353    if (provider == null)
354      throw new IllegalArgumentException();
355
356    for (int i = 0; i < categories.length; i++)
357      if (categories[i].isInstance(provider))
358        {
359          ok = true;
360          registerServiceProvider(provider, i);
361        }
362
363    if (!ok)
364      throw new IllegalArgumentException();
365  }
366
367
368  /**
369   * Registers a number of providers under all service categories they
370   * implement.
371   *
372   * <p>If a provider implements the {@link RegisterableService}
373   * interface, its {@link RegisterableService#onRegistration
374   * onRegistration} method is invoked in order to inform the provider
375   * about the addition to this registry. If <code>provider</code>
376   * implements several service categories,
377   * <code>onRegistration</code> gets called multiple times.
378   *
379   * @throws IllegalArgumentException if <code>providers</code> is
380   * <code>null</code>, if any iterated provider is <code>null</code>,
381   * or if some iterated provider does not implement any of the
382   * service categories passed to the {@linkplain
383   * #ServiceRegistry(Iterator) constructor} of this
384   * <code>ServiceRegistry</code>.
385   */
386  public synchronized void registerServiceProviders(Iterator<?> providers)
387  {
388    if (providers == null)
389      throw new IllegalArgumentException();
390
391    while (providers.hasNext())
392      registerServiceProvider(providers.next());
393  }
394
395
396  /**
397   * De-registers a provider for a service category which is specified
398   * by the class-internal category ID.
399   *
400   * @param provider the service provider to be registered.
401   *
402   * @param cat the service category, which is identified by an index
403   * into the {@link #categories} array.
404   *
405   * @return <code>true</code> if <code>provider</code> was previously
406   * registered for the specified service category; <code>false</code>
407   * if if the provider had not been registered.
408   *
409   * @throws IllegalArgumentException if <code>provider</code> is
410   * <code>null</code>.
411   *
412   * @throws ClassCastException if <code>provider</code> does not
413   * implement the specified service provider interface.
414   */
415  private synchronized boolean deregisterServiceProvider(Object provider,
416                                                         int cat)
417  {
418    LinkedList provs;
419    boolean result;
420    Class category;
421
422    if (provider == null)
423      throw new IllegalArgumentException();
424
425    category = categories[cat];
426    if (!category.isInstance(provider))
427      throw new ClassCastException(category.getName());
428
429    provs = providers[cat];
430    if (provs == null)
431      return false;
432
433    result = provs.remove(provider);
434    if (provs.isEmpty())
435      providers[cat] = null;
436
437    if (result && (provider instanceof RegisterableService))
438      ((RegisterableService) provider).onDeregistration(this, category);
439
440    return result;
441  }
442
443
444  /**
445   * De-registers a provider for the specified service category.
446   *
447   * <p>If <code>provider</code> implements the {@link
448   * RegisterableService} interface, its {@link
449   * RegisterableService#onDeregistration onDeregistration} method is
450   * invoked in order to inform the provider about the removal from
451   * this registry.
452   *
453   * @param provider the service provider to be de-registered.
454   *
455   * @param category the service category from which
456   * <code>provider</code> shall be de-registered.
457   *
458   * @return <code>true</code> if <code>provider</code> was previously
459   * registered for the specified service category; <code>false</code>
460   * if if the provider had not been registered.
461   *
462   * @throws IllegalArgumentException if <code>provider</code> is
463   * <code>null</code>, or if <code>category</code> is not among the
464   * categories passed to the {@linkplain #ServiceRegistry(Iterator)
465   * constructor} of this ServiceRegistry.
466   *
467   * @throws ClassCastException if <code>provider</code> does not
468   * implement <code>category</code>.
469   */
470  public synchronized <T> boolean deregisterServiceProvider(T provider,
471                                                            Class<T> category)
472  {
473    for (int i = 0; i < categories.length; i++)
474      if (categories[i] == category)
475        return deregisterServiceProvider(provider, i);
476    throw new IllegalArgumentException();
477  }
478
479
480  /**
481   * De-registers a provider from all service categories it
482   * implements.
483   *
484   * <p>If <code>provider</code> implements the {@link
485   * RegisterableService} interface, its {@link
486   * RegisterableService#onDeregistration onDeregistration} method is
487   * invoked in order to inform the provider about the removal from
488   * this registry. If <code>provider</code> implements several
489   * service categories, <code>onDeregistration</code> gets called
490   * multiple times.</p>
491   *
492   * @param provider the service provider to be de-registered.
493   *
494   * @throws IllegalArgumentException if <code>provider</code> is
495   * <code>null</code>, or if <code>provider</code> does not implement
496   * any of the service categories passed to the {@linkplain
497   * #ServiceRegistry(Iterator) constructor} of this
498   * <code>ServiceRegistry</code>.
499   */
500  public synchronized void deregisterServiceProvider(Object provider)
501  {
502    boolean ok = false;
503
504    if (provider == null)
505      throw new IllegalArgumentException();
506
507    for (int i = 0; i < categories.length; i++)
508      if (categories[i].isInstance(provider))
509        {
510          ok = true;
511          deregisterServiceProvider(provider, i);
512        }
513
514    if (!ok)
515      throw new IllegalArgumentException();
516  }
517
518
519  /**
520   * De-registers all providers which have been registered for the
521   * specified service category.
522   *
523   * <p>If a provider implements the {@link RegisterableService}
524   * interface, its {@link RegisterableService#onDeregistration
525   * onDeregistration} method is invoked in order to inform the
526   * provider about the removal from this registry. If the provider
527   * implements several service categories,
528   * <code>onDeregistration</code> gets called multiple times.
529   *
530   * @param category the category whose registered providers will be
531   * de-registered.
532   *
533   * @throws IllegalArgumentException if <code>category</code> is not
534   * among the categories passed to the {@linkplain
535   * #ServiceRegistry(Iterator) constructor} of this
536   * <code>ServiceRegistry</code>.
537   */
538  public synchronized void deregisterAll(Class<?> category)
539  {
540    boolean ok = false;
541
542    for (int i = 0; i < categories.length; i++)
543      {
544        if (categories[i] != category)
545          continue;
546
547        ok = true;
548        while (providers[i] != null)
549          deregisterServiceProvider(providers[i].get(0), i);
550      }
551
552    if (!ok)
553      throw new IllegalArgumentException();
554  }
555
556
557  /**
558   * De-registers all service providers.
559   *
560   * <p>If a provider implements the {@link RegisterableService}
561   * interface, its {@link RegisterableService#onDeregistration
562   * onDeregistration} method is invoked in order to inform the
563   * provider about the removal from this registry. If the provider
564   * implements several service categories,
565   * <code>onDeregistration</code> gets called multiple times.
566   */
567  public synchronized void deregisterAll()
568  {
569    for (int i = 0; i < categories.length; i++)
570      while (providers[i] != null)
571        deregisterServiceProvider(providers[i].get(0), i);
572  }
573
574
575  /**
576   * Called by the Virtual Machine when it detects that this
577   * <code>ServiceRegistry</code> has become garbage. De-registers all
578   * service providers, which will cause those that implement {@link
579   * RegisterableService} to receive a {@link
580   * RegisterableService#onDeregistration onDeregistration}
581   * notification.
582   */
583  public void finalize()
584    throws Throwable
585  {
586    super.finalize();
587    deregisterAll();
588  }
589
590
591  /**
592   * Determines whether a provider has been registered with this
593   * registry.
594   *
595   * @return <code>true</code> if <code>provider</code> has been
596   * registered under any service category; <code>false</code> if
597   * it is not registered.
598   *
599   * @throws IllegalArgumentException if <code>provider</code> is
600   * <code>null</code>.
601   */
602  public synchronized boolean contains(Object provider)
603  {
604    if (provider == null)
605      throw new IllegalArgumentException();
606
607    // Note that contains is rather unlikely to be ever called,
608    // so it would be wasteful to keep a special data structure
609    // (such as a HashSet) for making it a fast operation.
610    for (int i = 0; i < providers.length; i++)
611      {
612        // If provider does not implement categories[i],
613        // it would not have been possible to register it there.
614        // In that case, it would be pointless to look there.
615        if (!categories[i].isInstance(provider))
616          continue;
617
618        // But if the list of registered providers contains provider,
619        // we have found it.
620        LinkedList p = providers[i];
621        if (p != null && p.contains(provider))
622          return true;
623      }
624
625    return false;
626  }
627
628
629  /**
630   * Returns the index in {@link #categories} occupied by the
631   * specified service category.
632   *
633   * @throws IllegalArgumentException if <code>category</code> is not
634   * among the categories passed to the {@linkplain
635   * #ServiceRegistry(Iterator) constructor} of this ServiceRegistry.
636   */
637  private int getCategoryID(Class category)
638  {
639    for (int i = 0; i < categories.length; i++)
640      if (categories[i] == category)
641        return i;
642
643    throw new IllegalArgumentException();
644  }
645
646
647  /**
648   * Retrieves all providers that have been registered for the
649   * specified service category.
650   *
651   * @param category the service category whose providers are
652   * to be retrieved.
653   *
654   * @param useOrdering <code>true</code> in order to retrieve the
655   * providers in an order imposed by the {@linkplain #setOrdering
656   * ordering constraints}; <code>false</code> in order to retrieve
657   * the providers in any order.
658   *
659   * @throws IllegalArgumentException if <code>category</code> is not
660   * among the categories passed to the {@linkplain
661   * #ServiceRegistry(Iterator) constructor} of this
662   * <code>ServiceRegistry</code>.
663   *
664   * @see #getServiceProviders(Class, Filter, boolean)
665   */
666  public <T> Iterator<T> getServiceProviders(Class<T> category,
667                                             boolean useOrdering)
668  {
669    return getServiceProviders(category, null, useOrdering);
670  }
671
672
673  /**
674   * Retrieves all providers that have been registered for the
675   * specified service category and that satisfy the criteria
676   * of a custom filter.
677   *
678   * @param category the service category whose providers are
679   * to be retrieved.
680   *
681   * @param filter a custom filter, or <code>null</code> to
682   * retrieve all registered providers for the specified
683   * category.
684   *
685   * @param useOrdering <code>true</code> in order to retrieve the
686   * providers in an order imposed by the {@linkplain #setOrdering
687   * ordering constraints}; <code>false</code> in order to retrieve
688   * the providers in any order.
689   *
690   * @throws IllegalArgumentException if <code>category</code> is not
691   * among the categories passed to the {@linkplain
692   * #ServiceRegistry(Iterator) constructor} of this
693   * <code>ServiceRegistry</code>.
694   */
695  public synchronized <T> Iterator<T> getServiceProviders(Class<T> category,
696                                                          Filter filter,
697                                                          boolean useOrdering)
698  {
699    int catid;
700    LinkedList provs;
701    ArrayList result;
702
703    catid = getCategoryID(category);
704    provs = providers[catid];
705    if (provs == null)
706      return Collections.EMPTY_LIST.iterator();
707
708    result = new ArrayList(provs.size());
709    for (Iterator iter = provs.iterator(); iter.hasNext();)
710      {
711        Object provider = iter.next();
712        if (filter == null || filter.filter(provider))
713          result.add(provider);
714      }
715
716    // If we are supposed to obey ordering constraints, and
717    // if any constraints have been imposed on the specified
718    // service category, sort the result.
719    if (useOrdering && constraints != null)
720      {
721        final Map cons = constraints[catid];
722        if (cons != null)
723          Collections.sort(result, new Comparator()
724            {
725              public int compare(Object o1, Object o2)
726              {
727                Set s;
728
729                if (o1 == o2)
730                  return 0;
731
732                s = (Set) cons.get(o1);
733                if (s != null && s.contains(o2))
734                  return -1;  // o1 < o2
735
736                s = (Set) cons.get(o2);
737                if (s != null && s.contains(o1))
738                  return 1;  // o1 > o2
739
740                return 0; // o1 == o2
741              }
742            });
743      }
744
745    return result.iterator();
746  }
747
748
749  /**
750   * Returns one of the service providers that is a subclass of the
751   * specified class.
752   *
753   * @param providerClass a class to search for.
754   */
755  public synchronized <T> T getServiceProviderByClass(Class<T> providerClass)
756  {
757    if (providerClass == null)
758      throw new IllegalArgumentException();
759
760    // Note that the method getServiceProviderByClass is rather
761    // unlikely to be ever called, so it would be wasteful to keep a
762    // special data structure for making it a fast operation.
763    for (int cat = 0; cat < categories.length; cat++)
764      {
765        if (!categories[cat].isAssignableFrom(providerClass))
766          continue;
767
768        LinkedList provs = providers[cat];
769        if (provs == null)
770          continue;
771
772        for (Iterator iter = provs.iterator(); iter.hasNext();)
773          {
774            Object provider = iter.next();
775            if (providerClass.isInstance(provider))
776              return (T) provider;
777          }
778      }
779
780    return null;
781  }
782
783
784  /**
785   * Adds an ordering constraint on service providers.
786   *
787   * @param category the service category to which an ordering
788   * constraint is to be added.
789   *
790   * @param firstProvider the provider which is supposed to come before
791   * <code>second</code>.
792   *
793   * @param secondProvider the provider which is supposed to come after
794   * <code>first</code>.
795   *
796   * @throws IllegalArgumentException if <code>first</code> and
797   * <code>second</code> are referring to the same object, or if one
798   * of them is <code>null</code>.
799   *
800   * @see #unsetOrdering
801   * @see #getServiceProviders(Class, Filter, boolean)
802   */
803  public synchronized <T> boolean setOrdering(Class<T> category,
804                                              T firstProvider,
805                                              T secondProvider)
806  {
807    return addConstraint(getCategoryID(category), firstProvider,
808                         secondProvider);
809  }
810
811
812  /**
813   * Removes an ordering constraint on service providers.
814   *
815   * @param category the service category from which an ordering
816   * constraint is to be removed.
817   *
818   * @param firstProvider the provider which is supposed to come before
819   * <code>second</code>.
820   *
821   * @param secondProvider the provider which is supposed to come after
822   * <code>first</code>.
823   *
824   * @throws IllegalArgumentException if <code>first</code> and
825   * <code>second</code> are referring to the same object, or if one
826   * of them is <code>null</code>.
827   *
828   * @see #setOrdering
829   */
830  public synchronized <T> boolean unsetOrdering(Class<T> category,
831                                                T firstProvider,
832                                                T secondProvider)
833  {
834    return removeConstraint(getCategoryID(category),
835                            firstProvider, secondProvider);
836  }
837
838
839  /**
840   * Adds an ordering constraint on service providers.
841   *
842   * @param catid the service category ID, which is the
843   * category&#x2019;s index into the {@link #categories} array.
844   *
845   * @param first the provider which is supposed to come before
846   * <code>second</code>.
847   *
848   * @param second the provider which is supposed to come after
849   * <code>first</code>.
850   *
851   * @throws IllegalArgumentException if <code>first</code> and
852   * <code>second</code> are referring to the same object, or if one
853   * of them is <code>null</code>.
854   */
855  private boolean addConstraint(int catid, Object first, Object second)
856  {
857    Set s;
858    IdentityHashMap cons;
859
860    // Also checks argument validity.
861    removeConstraint(catid, second, first);
862
863    if (constraints == null)
864      constraints = new IdentityHashMap[categories.length];
865    cons = constraints[catid];
866    if (cons == null)
867      cons = constraints[catid] = new IdentityHashMap();
868
869    s = (Set) cons.get(first);
870    if (s == null)
871      cons.put(first, s = new HashSet());
872    return s.add(second);
873  }
874
875
876  /**
877   * Removes an ordering constraint on service providers.
878   *
879   * @param catid the service category ID, which is the
880   * category&#x2019;s index into the {@link #categories} array.
881   *
882   * @param first the provider which is supposed to come before
883   * <code>second</code>.
884   *
885   * @param second the provider which is supposed to come after
886   * <code>first</code>.
887   *
888   * @throws IllegalArgumentException if <code>first</code> and
889   * <code>second</code> are referring to the same object, or if one
890   * of them is <code>null</code>.
891   */
892  private boolean removeConstraint(int catid, Object first, Object second)
893  {
894    Collection s;
895    IdentityHashMap cons;
896
897    if (first == null || second == null || first == second)
898      throw new IllegalArgumentException();
899
900    if (constraints == null)
901      return false;
902
903    cons = constraints[catid];
904    if (cons == null)
905      return false;
906
907    s = (Collection) cons.get(first);
908    if (s == null)
909      return false;
910
911    if (!s.remove(second))
912      return false;
913
914    // If we removed the last constraint for a service category,
915    // we can get free some memory.
916    if (cons.isEmpty())
917      {
918        constraints[catid] = null;
919        boolean anyConstraints = false;
920        for (int i = 0; i < constraints.length; i++)
921          {
922            if (constraints[i] != null)
923              {
924                anyConstraints = true;
925                break;
926              }
927          }
928        if (!anyConstraints)
929          constraints = null;
930      }
931
932    return true;
933  }
934
935
936  /**
937   * A filter for selecting service providers that match custom
938   * criteria.
939   *
940   * @see ServiceRegistry#getServiceProviders(Class, Filter,
941   * boolean)
942   *
943   * @since 1.4
944   *
945   * @author Michael Koch (konqueror@gmx.de)
946   * @author Sascha Brawer (brawer@dandelis.ch)
947   */
948  public static interface Filter
949  {
950    /**
951     * Checks whether the specified service provider matches the
952     * constraints of this Filter.
953     *
954     * @param provider the service provider in question.
955     *
956     * @return <code>true</code> if <code>provider</code> matches the
957     * criteria; <code>false</code> if it does not match.
958     */
959    boolean filter(Object provider);
960  }
961}