001    /* ServiceRegistry.java -- A simple registry for service providers.
002       Copyright (C) 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.imageio.spi;
040    
041    import gnu.classpath.ServiceFactory;
042    
043    import java.util.ArrayList;
044    import java.util.Collection;
045    import java.util.Collections;
046    import java.util.Comparator;
047    import java.util.HashSet;
048    import java.util.IdentityHashMap;
049    import java.util.Iterator;
050    import java.util.LinkedList;
051    import java.util.Map;
052    import java.util.NoSuchElementException;
053    import 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     */
063    public 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    }