1 /*
  2  * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package javax.imageio.spi;
 27 
 28 import java.io.File;
 29 import java.security.AccessControlContext;
 30 import java.security.AccessController;
 31 import java.security.PrivilegedAction;
 32 import java.util.ArrayList;
 33 import java.util.HashMap;
 34 import java.util.Iterator;
 35 import java.util.List;
 36 import java.util.Map;
 37 import java.util.NoSuchElementException;
 38 import java.util.Set;
 39 import java.util.ServiceLoader;
 40 
 41 /**
 42  * A registry for service provider instances for Image I/O service types.
 43  *
 44  * <p> Service providers are stored in one or more <i>categories</i>,
 45  * each of which is defined by a class or interface (described by a
 46  * {@code Class} object) that all of its members must implement.
 47  *
 48  * <p>The set of categories supported by this class is limited
 49  * to the following standard Image I/O service types:
 50  *
 51  * <ul>
 52  * <li>{@link ImageInputStreamSpi}
 53  * <li>{@link ImageOutputStreamSpi}
 54  * <li>{@link ImageReaderSpi}
 55  * <li>{@link ImageTranscoderSpi}
 56  * <li>{@link ImageWriterSpi}
 57  * </ul>
 58  *
 59  * <p>An attempt to load a provider that is not a subtype of one of the
 60  * above types will result in {@code IllegalArgumentException}.
 61  * <p> For the general mechanism to load service providers, see
 62  * {@link java.util.ServiceLoader ServiceLoader}, which is
 63  * the underlying standard mechanism used by this class.
 64  *
 65  * <p> Only a single instance of a given leaf class (that is, the
 66  * actual class returned by {@code getClass()}, as opposed to any
 67  * inherited classes or interfaces) may be registered.  That is,
 68  * suppose that the
 69  * {@code com.mycompany.mypkg.GreenImageReaderProvider} class
 70  * is a subclass of {@code javax.imageio.spi.ImageReaderSpi}.
 71  * If a {@code GreenImageReaderProvider} instance is
 72  * registered, it will be stored in the category defined by the
 73  * {@code ImageReaderSpi} class.  If a new instance of
 74  * {@code GreenImageReaderProvider} is registered, it will replace
 75  * the previous instance.  In practice, service provider objects are
 76  * usually singletons so this behavior is appropriate.
 77  *
 78  * <p> The service provider classes should be lightweight and
 79  * quick to load.  Implementations of these interfaces should avoid
 80  * complex dependencies on other classes and on native code. The usual
 81  * pattern for more complex services is to register a lightweight
 82  * proxy for the heavyweight service.
 83  *
 84  * <p> An application may customize the contents of a registry as it
 85  * sees fit, so long as it has the appropriate runtime permission.
 86  *
 87  * <p> For information on how to create and deploy service providers,
 88  * refer to the documentation on {@link java.util.ServiceLoader ServiceLoader}
 89  *
 90  * @see RegisterableService
 91  * @see java.util.ServiceLoader
 92  */
 93 public class ServiceRegistry {
 94 
 95     // Class -> Registry
 96     private Map<Class<?>, SubRegistry> categoryMap = new HashMap<>();
 97 
 98     /**
 99      * Constructs a {@code ServiceRegistry} instance with a
100      * set of categories taken from the {@code categories}
101      * argument. The categories must all be members of the set
102      * of service types listed in the class specification.
103      *
104      * @param categories an {@code Iterator} containing
105      * {@code Class} objects to be used to define categories.
106      *
107      * @exception IllegalArgumentException if
108      * {@code categories} is {@code null}, or if
109      * one of the categories is not an allowed service type.
110      */
111     public ServiceRegistry(Iterator<Class<?>> categories) {
112         if (categories == null) {
113             throw new IllegalArgumentException("categories == null!");
114         }
115         while (categories.hasNext()) {
116             Class<?> category = categories.next();
117             checkClassAllowed(category);
118             SubRegistry reg = new SubRegistry(this, category);
119             categoryMap.put(category, reg);
120         }
121     }
122 
123     /**
124      * Searches for implementations of a particular service class
125      * using the given class loader.
126      *
127      * <p>The service class must be one of the service types listed
128      * in the class specification. If it is not, {@code IllegalArgumentException}
129      * will be thrown.
130      *
131      * <p> This method transforms the name of the given service class
132      * into a provider-configuration filename as described in the
133      * class comment and then uses the {@code getResources}
134      * method of the given class loader to find all available files
135      * with that name.  These files are then read and parsed to
136      * produce a list of provider-class names.  The iterator that is
137      * returned uses the given class loader to look up and then
138      * instantiate each element of the list.
139      *
140      * <p> Because it is possible for extensions to be installed into
141      * a running Java virtual machine, this method may return
142      * different results each time it is invoked.
143      *
144      * @param providerClass a {@code Class} object indicating the
145      * class or interface of the service providers being detected.
146      *
147      * @param loader the class loader to be used to load
148      * provider-configuration files and instantiate provider classes,
149      * or {@code null} if the system class loader (or, failing that
150      * the bootstrap class loader) is to be used.
151      *
152      * @param <T> the type of the providerClass.
153      *
154      * @return An {@code Iterator} that yields provider objects
155      * for the given service, in some arbitrary order.  The iterator
156      * will throw an {@code Error} if a provider-configuration
157      * file violates the specified format or if a provider class
158      * cannot be found and instantiated.
159      *
160      * @exception IllegalArgumentException if
161      * {@code providerClass} is {@code null}, or if it is
162      * not one of the allowed service types.
163      */
164     public static <T> Iterator<T> lookupProviders(Class<T> providerClass,
165                                                   ClassLoader loader)
166     {
167         if (providerClass == null) {
168             throw new IllegalArgumentException("providerClass == null!");
169         }
170         checkClassAllowed(providerClass);
171         return ServiceLoader.load(providerClass, loader).iterator();
172     }
173 
174     /**
175      * Locates and incrementally instantiates the available providers
176      * of a given service using the context class loader.  This
177      * convenience method is equivalent to:
178      *
179      * <pre>
180      *   ClassLoader cl = Thread.currentThread().getContextClassLoader();
181      *   return Service.providers(service, cl);
182      * </pre>
183      *
184      * <p>The service class must be one of the service types listed
185      * in the class specification. If it is not, {@code IllegalArgumentException}
186      * will be thrown.
187      *
188      * @param providerClass a {@code Class} object indicating the
189      * class or interface of the service providers being detected.
190      *
191      * @param <T> the type of the providerClass.
192      *
193      * @return An {@code Iterator} that yields provider objects
194      * for the given service, in some arbitrary order.  The iterator
195      * will throw an {@code Error} if a provider-configuration
196      * file violates the specified format or if a provider class
197      * cannot be found and instantiated.
198      *
199      * @exception IllegalArgumentException if
200      * {@code providerClass} is {@code null}, or if it is
201      * not one of the allowed service types.
202      */
203     public static <T> Iterator<T> lookupProviders(Class<T> providerClass) {
204         if (providerClass == null) {
205             throw new IllegalArgumentException("providerClass == null!");
206         }
207         checkClassAllowed(providerClass);
208         return ServiceLoader.load(providerClass).iterator();
209     }
210 
211     /**
212      * Returns an {@code Iterator} of {@code Class} objects
213      * indicating the current set of categories.  The iterator will be
214      * empty if no categories exist.
215      *
216      * @return an {@code Iterator} containing
217      * {@code Class} objects.
218      */
219     public Iterator<Class<?>> getCategories() {
220         Set<Class<?>> keySet = categoryMap.keySet();
221         return keySet.iterator();
222     }
223 
224     /**
225      * Returns an Iterator containing the subregistries to which the
226      * provider belongs.
227      */
228     private Iterator<SubRegistry> getSubRegistries(Object provider) {
229         List<SubRegistry> l = new ArrayList<>();
230         for (Class<?> c : categoryMap.keySet()) {
231             if (c.isAssignableFrom(provider.getClass())) {
232                 l.add(categoryMap.get(c));
233             }
234         }
235         return l.iterator();
236     }
237 
238     /**
239      * Adds a service provider object to the registry.  The provider
240      * is associated with the given category.
241      *
242      * <p> If {@code provider} implements the
243      * {@code RegisterableService} interface, its
244      * {@code onRegistration} method will be called.  Its
245      * {@code onDeregistration} method will be called each time
246      * it is deregistered from a category, for example if a
247      * category is removed or the registry is garbage collected.
248      *
249      * @param provider the service provide object to be registered.
250      * @param category the category under which to register the
251      * provider.
252      * @param <T> the type of the provider.
253      *
254      * @return true if no provider of the same class was previously
255      * registered in the same category category.
256      *
257      * @exception IllegalArgumentException if {@code provider} is
258      * {@code null}.
259      * @exception IllegalArgumentException if there is no category
260      * corresponding to {@code category}.
261      * @exception ClassCastException if provider does not implement
262      * the {@code Class} defined by {@code category}.
263      */
264     public <T> boolean registerServiceProvider(T provider,
265                                                Class<T> category) {
266         if (provider == null) {
267             throw new IllegalArgumentException("provider == null!");
268         }
269         SubRegistry reg = categoryMap.get(category);
270         if (reg == null) {
271             throw new IllegalArgumentException("category unknown!");
272         }
273         if (!category.isAssignableFrom(provider.getClass())) {
274             throw new ClassCastException();
275         }
276 
277         return reg.registerServiceProvider(provider);
278     }
279 
280     /**
281      * Adds a service provider object to the registry.  The provider
282      * is associated within each category present in the registry
283      * whose {@code Class} it implements.
284      *
285      * <p> If {@code provider} implements the
286      * {@code RegisterableService} interface, its
287      * {@code onRegistration} method will be called once for each
288      * category it is registered under.  Its
289      * {@code onDeregistration} method will be called each time
290      * it is deregistered from a category or when the registry is
291      * finalized.
292      *
293      * @param provider the service provider object to be registered.
294      *
295      * @exception IllegalArgumentException if
296      * {@code provider} is {@code null}.
297      */
298     public void registerServiceProvider(Object provider) {
299         if (provider == null) {
300             throw new IllegalArgumentException("provider == null!");
301         }
302         Iterator<SubRegistry> regs = getSubRegistries(provider);
303         while (regs.hasNext()) {
304             SubRegistry reg = regs.next();
305             reg.registerServiceProvider(provider);
306         }
307     }
308 
309     /**
310      * Adds a set of service provider objects, taken from an
311      * {@code Iterator} to the registry.  Each provider is
312      * associated within each category present in the registry whose
313      * {@code Class} it implements.
314      *
315      * <p> For each entry of {@code providers} that implements
316      * the {@code RegisterableService} interface, its
317      * {@code onRegistration} method will be called once for each
318      * category it is registered under.  Its
319      * {@code onDeregistration} method will be called each time
320      * it is deregistered from a category or when the registry is
321      * finalized.
322      *
323      * @param providers an Iterator containing service provider
324      * objects to be registered.
325      *
326      * @exception IllegalArgumentException if {@code providers}
327      * is {@code null} or contains a {@code null} entry.
328      */
329     public void registerServiceProviders(Iterator<?> providers) {
330         if (providers == null) {
331             throw new IllegalArgumentException("provider == null!");
332         }
333         while (providers.hasNext()) {
334             registerServiceProvider(providers.next());
335         }
336     }
337 
338     /**
339      * Removes a service provider object from the given category.  If
340      * the provider was not previously registered, nothing happens and
341      * {@code false} is returned.  Otherwise, {@code true}
342      * is returned.  If an object of the same class as
343      * {@code provider} but not equal (using {@code ==}) to
344      * {@code provider} is registered, it will not be
345      * deregistered.
346      *
347      * <p> If {@code provider} implements the
348      * {@code RegisterableService} interface, its
349      * {@code onDeregistration} method will be called.
350      *
351      * @param provider the service provider object to be deregistered.
352      * @param category the category from which to deregister the
353      * provider.
354      * @param <T> the type of the provider.
355      *
356      * @return {@code true} if the provider was previously
357      * registered in the same category category,
358      * {@code false} otherwise.
359      *
360      * @exception IllegalArgumentException if {@code provider} is
361      * {@code null}.
362      * @exception IllegalArgumentException if there is no category
363      * corresponding to {@code category}.
364      * @exception ClassCastException if provider does not implement
365      * the class defined by {@code category}.
366      */
367     public <T> boolean deregisterServiceProvider(T provider,
368                                                  Class<T> category) {
369         if (provider == null) {
370             throw new IllegalArgumentException("provider == null!");
371         }
372         SubRegistry reg = categoryMap.get(category);
373         if (reg == null) {
374             throw new IllegalArgumentException("category unknown!");
375         }
376         if (!category.isAssignableFrom(provider.getClass())) {
377             throw new ClassCastException();
378         }
379         return reg.deregisterServiceProvider(provider);
380     }
381 
382     /**
383      * Removes a service provider object from all categories that
384      * contain it.
385      *
386      * @param provider the service provider object to be deregistered.
387      *
388      * @exception IllegalArgumentException if {@code provider} is
389      * {@code null}.
390      */
391     public void deregisterServiceProvider(Object provider) {
392         if (provider == null) {
393             throw new IllegalArgumentException("provider == null!");
394         }
395         Iterator<SubRegistry> regs = getSubRegistries(provider);
396         while (regs.hasNext()) {
397             SubRegistry reg = regs.next();
398             reg.deregisterServiceProvider(provider);
399         }
400     }
401 
402     /**
403      * Returns {@code true} if {@code provider} is currently
404      * registered.
405      *
406      * @param provider the service provider object to be queried.
407      *
408      * @return {@code true} if the given provider has been
409      * registered.
410      *
411      * @exception IllegalArgumentException if {@code provider} is
412      * {@code null}.
413      */
414     public boolean contains(Object provider) {
415         if (provider == null) {
416             throw new IllegalArgumentException("provider == null!");
417         }
418         Iterator<SubRegistry> regs = getSubRegistries(provider);
419         while (regs.hasNext()) {
420             SubRegistry reg = regs.next();
421             if (reg.contains(provider)) {
422                 return true;
423             }
424         }
425 
426         return false;
427     }
428 
429     /**
430      * Returns an {@code Iterator} containing all registered
431      * service providers in the given category.  If
432      * {@code useOrdering} is {@code false}, the iterator
433      * will return all of the server provider objects in an arbitrary
434      * order.  Otherwise, the ordering will respect any pairwise
435      * orderings that have been set.  If the graph of pairwise
436      * orderings contains cycles, any providers that belong to a cycle
437      * will not be returned.
438      *
439      * @param category the category to be retrieved from.
440      * @param useOrdering {@code true} if pairwise orderings
441      * should be taken account in ordering the returned objects.
442      * @param <T> the type of the category.
443      *
444      * @return an {@code Iterator} containing service provider
445      * objects from the given category, possibly in order.
446      *
447      * @exception IllegalArgumentException if there is no category
448      * corresponding to {@code category}.
449      */
450     public <T> Iterator<T> getServiceProviders(Class<T> category,
451                                                boolean useOrdering) {
452         SubRegistry reg = categoryMap.get(category);
453         if (reg == null) {
454             throw new IllegalArgumentException("category unknown!");
455         }
456         @SuppressWarnings("unchecked")
457         Iterator<T> it = (Iterator<T>)reg.getServiceProviders(useOrdering);
458         return it;
459     }
460 
461     /**
462      * A simple filter interface used by
463      * {@code ServiceRegistry.getServiceProviders} to select
464      * providers matching an arbitrary criterion.  Classes that
465      * implement this interface should be defined in order to make use
466      * of the {@code getServiceProviders} method of
467      * {@code ServiceRegistry} that takes a {@code Filter}.
468      *
469      * @see ServiceRegistry#getServiceProviders(Class, ServiceRegistry.Filter, boolean)
470      */
471     public interface Filter {
472 
473         /**
474          * Returns {@code true} if the given
475          * {@code provider} object matches the criterion defined
476          * by this {@code Filter}.
477          *
478          * @param provider a service provider {@code Object}.
479          *
480          * @return true if the provider matches the criterion.
481          */
482         boolean filter(Object provider);
483     }
484 
485     /**
486      * Returns an {@code Iterator} containing service provider
487      * objects within a given category that satisfy a criterion
488      * imposed by the supplied {@code ServiceRegistry.Filter}
489      * object's {@code filter} method.
490      *
491      * <p> The {@code useOrdering} argument controls the
492      * ordering of the results using the same rules as
493      * {@code getServiceProviders(Class, boolean)}.
494      *
495      * @param category the category to be retrieved from.
496      * @param filter an instance of {@code ServiceRegistry.Filter}
497      * whose {@code filter} method will be invoked.
498      * @param useOrdering {@code true} if pairwise orderings
499      * should be taken account in ordering the returned objects.
500      * @param <T> the type of the category.
501      *
502      * @return an {@code Iterator} containing service provider
503      * objects from the given category, possibly in order.
504      *
505      * @exception IllegalArgumentException if there is no category
506      * corresponding to {@code category}.
507      */
508     public <T> Iterator<T> getServiceProviders(Class<T> category,
509                                                Filter filter,
510                                                boolean useOrdering) {
511         SubRegistry reg = categoryMap.get(category);
512         if (reg == null) {
513             throw new IllegalArgumentException("category unknown!");
514         }
515         Iterator<T> iter = getServiceProviders(category, useOrdering);
516         return new FilterIterator<>(iter, filter);
517     }
518 
519     /**
520      * Returns the currently registered service provider object that
521      * is of the given class type.  At most one object of a given
522      * class is allowed to be registered at any given time.  If no
523      * registered object has the desired class type, {@code null}
524      * is returned.
525      *
526      * @param providerClass the {@code Class} of the desired
527      * service provider object.
528      * @param <T> the type of the provider.
529      *
530      * @return a currently registered service provider object with the
531      * desired {@code Class} type, or {@code null} is none is
532      * present.
533      *
534      * @exception IllegalArgumentException if {@code providerClass} is
535      * {@code null}.
536      */
537     public <T> T getServiceProviderByClass(Class<T> providerClass) {
538         if (providerClass == null) {
539             throw new IllegalArgumentException("providerClass == null!");
540         }
541         for (Class<?> c : categoryMap.keySet()) {
542             if (c.isAssignableFrom(providerClass)) {
543                 SubRegistry reg = categoryMap.get(c);
544                 T provider = reg.getServiceProviderByClass(providerClass);
545                 if (provider != null) {
546                     return provider;
547                 }
548             }
549         }
550         return null;
551     }
552 
553     /**
554      * Sets a pairwise ordering between two service provider objects
555      * within a given category.  If one or both objects are not
556      * currently registered within the given category, or if the
557      * desired ordering is already set, nothing happens and
558      * {@code false} is returned.  If the providers previously
559      * were ordered in the reverse direction, that ordering is
560      * removed.
561      *
562      * <p> The ordering will be used by the
563      * {@code getServiceProviders} methods when their
564      * {@code useOrdering} argument is {@code true}.
565      *
566      * @param category a {@code Class} object indicating the
567      * category under which the preference is to be established.
568      * @param firstProvider the preferred provider.
569      * @param secondProvider the provider to which
570      * {@code firstProvider} is preferred.
571      * @param <T> the type of the category.
572      *
573      * @return {@code true} if a previously unset ordering
574      * was established.
575      *
576      * @exception IllegalArgumentException if either provider is
577      * {@code null} or they are the same object.
578      * @exception IllegalArgumentException if there is no category
579      * corresponding to {@code category}.
580      */
581     public <T> boolean setOrdering(Class<T> category,
582                                    T firstProvider,
583                                    T secondProvider) {
584         if (firstProvider == null || secondProvider == null) {
585             throw new IllegalArgumentException("provider is null!");
586         }
587         if (firstProvider == secondProvider) {
588             throw new IllegalArgumentException("providers are the same!");
589         }
590         SubRegistry reg = categoryMap.get(category);
591         if (reg == null) {
592             throw new IllegalArgumentException("category unknown!");
593         }
594         if (reg.contains(firstProvider) &&
595             reg.contains(secondProvider)) {
596             return reg.setOrdering(firstProvider, secondProvider);
597         }
598         return false;
599     }
600 
601     /**
602      * Sets a pairwise ordering between two service provider objects
603      * within a given category.  If one or both objects are not
604      * currently registered within the given category, or if no
605      * ordering is currently set between them, nothing happens
606      * and {@code false} is returned.
607      *
608      * <p> The ordering will be used by the
609      * {@code getServiceProviders} methods when their
610      * {@code useOrdering} argument is {@code true}.
611      *
612      * @param category a {@code Class} object indicating the
613      * category under which the preference is to be disestablished.
614      * @param firstProvider the formerly preferred provider.
615      * @param secondProvider the provider to which
616      * {@code firstProvider} was formerly preferred.
617      * @param <T> the type of the category.
618      *
619      * @return {@code true} if a previously set ordering was
620      * disestablished.
621      *
622      * @exception IllegalArgumentException if either provider is
623      * {@code null} or they are the same object.
624      * @exception IllegalArgumentException if there is no category
625      * corresponding to {@code category}.
626      */
627     public <T> boolean unsetOrdering(Class<T> category,
628                                      T firstProvider,
629                                      T secondProvider) {
630         if (firstProvider == null || secondProvider == null) {
631             throw new IllegalArgumentException("provider is null!");
632         }
633         if (firstProvider == secondProvider) {
634             throw new IllegalArgumentException("providers are the same!");
635         }
636         SubRegistry reg = categoryMap.get(category);
637         if (reg == null) {
638             throw new IllegalArgumentException("category unknown!");
639         }
640         if (reg.contains(firstProvider) &&
641             reg.contains(secondProvider)) {
642             return reg.unsetOrdering(firstProvider, secondProvider);
643         }
644         return false;
645     }
646 
647     /**
648      * Deregisters all service provider object currently registered
649      * under the given category.
650      *
651      * @param category the category to be emptied.
652      *
653      * @exception IllegalArgumentException if there is no category
654      * corresponding to {@code category}.
655      */
656     public void deregisterAll(Class<?> category) {
657         SubRegistry reg = categoryMap.get(category);
658         if (reg == null) {
659             throw new IllegalArgumentException("category unknown!");
660         }
661         reg.clear();
662     }
663 
664     /**
665      * Deregisters all currently registered service providers from all
666      * categories.
667      */
668     public void deregisterAll() {
669         for (SubRegistry reg : categoryMap.values()) {
670             reg.clear();
671         }
672     }
673 
674     /**
675      * Finalizes this object prior to garbage collection.  The
676      * {@code deregisterAll} method is called to deregister all
677      * currently registered service providers.  This method should not
678      * be called from application code.
679      *
680      * @exception Throwable if an error occurs during superclass
681      * finalization.
682      *
683      * @deprecated The {@code finalize} method has been deprecated.
684      *     Subclasses that override {@code finalize} in order to perform cleanup
685      *     should be modified to use alternative cleanup mechanisms and
686      *     to remove the overriding {@code finalize} method.
687      *     When overriding the {@code finalize} method, its implementation must explicitly
688      *     ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}.
689      *     See the specification for {@link Object#finalize()} for further
690      *     information about migration options.
691      */
692     @Deprecated(since="9")
693     public void finalize() throws Throwable {
694         deregisterAll();
695         super.finalize();
696     }
697 
698     /**
699      * Checks whether the provided class is one of the allowed
700      * ImageIO service provider classes. If it is, returns normally.
701      * If it is not, throws IllegalArgumentException.
702      *
703      * @param clazz
704      * @throws IllegalArgumentException if clazz is null or is not one of the allowed set
705      */
706     private static void checkClassAllowed(Class<?> clazz) {
707         if (clazz == null) {
708             throw new IllegalArgumentException("class must not be null");
709         }
710 
711         if (   clazz != ImageInputStreamSpi.class
712             && clazz != ImageOutputStreamSpi.class
713             && clazz != ImageReaderSpi.class
714             && clazz != ImageTranscoderSpi.class
715             && clazz != ImageWriterSpi.class) {
716             throw new IllegalArgumentException(clazz.getName() + " is not an ImageIO SPI class");
717         }
718     }
719 }
720 
721 
722 /**
723  * A portion of a registry dealing with a single superclass or
724  * interface.
725  */
726 class SubRegistry {
727 
728     ServiceRegistry registry;
729 
730     Class<?> category;
731 
732     // Provider Objects organized by partial ordering
733     final PartiallyOrderedSet<Object> poset = new PartiallyOrderedSet<>();
734 
735     // Class -> Provider Object of that class
736     // No way to express heterogeneous map, we want
737     // Map<Class<T>, T>, where T is ?
738     final Map<Class<?>, Object> map = new HashMap<>();
739     @SuppressWarnings("removal")
740     final Map<Class<?>, AccessControlContext> accMap = new HashMap<>();
741 
742     public SubRegistry(ServiceRegistry registry, Class<?> category) {
743         this.registry = registry;
744         this.category = category;
745     }
746 
747     @SuppressWarnings("removal")
748     public synchronized boolean registerServiceProvider(Object provider) {
749         Object oprovider = map.get(provider.getClass());
750         boolean present =  oprovider != null;
751 
752         if (present) {
753             deregisterServiceProvider(oprovider);
754         }
755         map.put(provider.getClass(), provider);
756         accMap.put(provider.getClass(), AccessController.getContext());
757         poset.add(provider);
758         if (provider instanceof RegisterableService) {
759             RegisterableService rs = (RegisterableService)provider;
760             try {
761                 rs.onRegistration(registry, category);
762             } catch (Throwable t) {
763                 System.err.println("Caught and handled this exception :");
764                 t.printStackTrace();
765             }
766         }
767 
768         return !present;
769     }
770 
771     /**
772      * If the provider was not previously registered, do nothing.
773      *
774      * @return true if the provider was previously registered.
775      */
776     public synchronized boolean deregisterServiceProvider(Object provider) {
777         Object oprovider = map.get(provider.getClass());
778 
779         if (provider == oprovider) {
780             map.remove(provider.getClass());
781             accMap.remove(provider.getClass());
782             poset.remove(provider);
783             if (provider instanceof RegisterableService) {
784                 RegisterableService rs = (RegisterableService)provider;
785                 rs.onDeregistration(registry, category);
786             }
787 
788             return true;
789         }
790         return false;
791     }
792 
793     public synchronized boolean contains(Object provider) {
794         Object oprovider = map.get(provider.getClass());
795         return oprovider == provider;
796     }
797 
798     public synchronized boolean setOrdering(Object firstProvider,
799                                             Object secondProvider) {
800         return poset.setOrdering(firstProvider, secondProvider);
801     }
802 
803     public synchronized boolean unsetOrdering(Object firstProvider,
804                                               Object secondProvider) {
805         return poset.unsetOrdering(firstProvider, secondProvider);
806     }
807 
808     public synchronized Iterator<Object> getServiceProviders
809                                          (boolean useOrdering) {
810         if (useOrdering) {
811             return poset.iterator();
812         } else {
813             return map.values().iterator();
814         }
815     }
816 
817     @SuppressWarnings("unchecked")
818     public synchronized <T> T getServiceProviderByClass
819                               (Class<T> providerClass) {
820         return (T)map.get(providerClass);
821     }
822 
823     @SuppressWarnings("removal")
824     public synchronized void clear() {
825         Iterator<Object> iter = map.values().iterator();
826         while (iter.hasNext()) {
827             Object provider = iter.next();
828             iter.remove();
829 
830             if (provider instanceof RegisterableService) {
831                 RegisterableService rs = (RegisterableService)provider;
832                 AccessControlContext acc = accMap.get(provider.getClass());
833                 if (acc != null || System.getSecurityManager() == null) {
834                     AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
835                     rs.onDeregistration(registry, category);
836                         return null;
837                     }, acc);
838                 }
839             }
840         }
841         poset.clear();
842         accMap.clear();
843     }
844 
845     @SuppressWarnings("deprecation")
846     public synchronized void finalize() {
847         clear();
848     }
849 }
850 
851 
852 /**
853  * A class for wrapping {@code Iterators} with a filter function.
854  * This provides an iterator for a subset without duplication.
855  */
856 class FilterIterator<T> implements Iterator<T> {
857 
858     private Iterator<? extends T> iter;
859     private ServiceRegistry.Filter filter;
860 
861     private T next = null;
862 
863     public FilterIterator(Iterator<? extends T> iter,
864                           ServiceRegistry.Filter filter) {
865         this.iter = iter;
866         this.filter = filter;
867         advance();
868     }
869 
870     private void advance() {
871         while (iter.hasNext()) {
872             T elt = iter.next();
873             if (filter.filter(elt)) {
874                 next = elt;
875                 return;
876             }
877         }
878 
879         next = null;
880     }
881 
882     public boolean hasNext() {
883         return next != null;
884     }
885 
886     public T next() {
887         if (next == null) {
888             throw new NoSuchElementException();
889         }
890         T o = next;
891         advance();
892         return o;
893     }
894 
895     public void remove() {
896         throw new UnsupportedOperationException();
897     }
898 }