/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2005-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.factory; import java.util.*; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Modifier; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import org.geotoolkit.resources.Errors; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.Classes; /** * A {@linkplain FactoryRegistry factory registry} having the additional capability to create new * factory instances on the fly. Instances can be created dynamically when the following conditions * are meet: * <p> * <ul> * <li>{@link FactoryRegistry#getServiceProvider FactoryRegistry.getServiceProvider(...)} * found no suitable instance for the given hints.<li> * <li>At least one registered factory has a public constructor expecting a single * {@link Hints} argument.</li> * </ul> * <p> * New factories are cached using {@linkplain WeakReference weak references}. Calls to * {@link #getServiceProvider getServiceProvider} first check if a previously created * factory can fit. * * @author Martin Desruisseaux (IRD, Geomatys) * @author Jody Garnett (Refractions) * @version 3.03 * * @since 2.1 * @module * * @deprecated Will be replaced by a more standard dependency injection mechanism. */ @Deprecated public class DynamicFactoryRegistry extends FactoryRegistry { /** * The array of classes for searching the one-argument constructor. */ private static final Class<?>[] HINTS_ARGUMENT = new Class<?>[] {Hints.class}; /** * List of factories already created. Used as a cache. */ private final Map<Class<?>, List<Reference<?>>> cache = new HashMap<>(); /** * Objects under construction for each implementation class. * Used by {@link #safeCreate} as a guard against infinite recursivity. */ private final Set<Class<?>> underConstruction = new HashSet<>(); /** * The factories which have been declared unavailable. Used in order to avoid * trying the same factory twice when we already failed in a previous attempt. */ private final Set<Class<? extends Factory>> unavailables = new HashSet<>(); /** * Constructs a new registry for the specified category. * * @param category The single category. * * @since 2.4 */ public DynamicFactoryRegistry(final Class<?> category) { super(category); } /** * Constructs a new registry for the specified categories. * * @param categories The categories. * * @since 2.4 */ public DynamicFactoryRegistry(final Class<?>[] categories) { super(categories); } /** * Constructs a new registry for the specified categories. * * @param categories The categories. */ public DynamicFactoryRegistry(final Collection<Class<?>> categories) { super(categories); } /** * Returns the providers available in the cache. * * @return The list of cached providers (never {@code null}). */ private <T> List<Reference<T>> getCachedProviders(final Class<T> category) { List<Reference<?>> c = cache.get(category); if (c == null) { c = new LinkedList<>(); cache.put(category, c); } @SuppressWarnings({"unchecked","rawtypes"}) final List<Reference<T>> cheat = (List) c; /* * Should be safe because we created an empty list, there is no other place where this * list is created and from this point we can only insert elements restricted to type T. */ return cheat; } /** * Caches the specified factory under the specified category. */ private <T> void cache(final Class<T> category, final T factory) { getCachedProviders(category).add(new WeakReference<>(factory)); } /** * Makes this {@code FactoryRegistry} ready for next use. */ @Override final void reset() { super.reset(); unavailables.clear(); } /** * Returns a provider for the specified category, using the specified map of hints (if any). * If a provider matching the requirements is found in the registry, it is returned. Otherwise, * a new provider is created and returned. This creation step is the only difference between * this method and the {@linkplain FactoryRegistry#getServiceProvider super-class method}. * * @param <T> The category to look for. * @param category The category to look for. * @param filter An optional filter, or {@code null} if none. * @param hints A {@linkplain Hints map of hints}, or {@code null} if none. * @param key The key to use for looking for a user-provided instance in the hints, or * {@code null} if none. * @return A factory for the specified category and hints (never {@code null}). * @throws FactoryNotFoundException if no factory was found, and the specified hints don't * provide sufficient information for creating a new factory. * @throws FactoryRegistryException if the factory can't be created for some other reason. */ @Override final <T> T getOrCreateServiceProvider(final Class<T> category, final Filter filter, Hints hints, final Hints.ClassKey key) throws FactoryRegistryException { final FactoryNotFoundException notFound; try { return super.getOrCreateServiceProvider(category, filter, hints, key); } catch (FactoryNotFoundException exception) { // Will be rethrown later in case of failure to create the factory. notFound = exception; } /* * No existing factory found. Creates one using reflection. First, we * check if an implementation class was explicitly specified by the user. */ final Class<?>[] types; if (hints == null || key == null) { types = null; } else { final Object hint = hints.get(key); if (hint == null) { types = null; } else { if (hint instanceof Class<?>[]) { types = (Class<?>[]) hint; } else { types = new Class<?>[] {(Class<?>) hint}; // Should not fail, since non-class argument should // have been accepted by 'super.getServiceProvider'. } /* * At this point we have a list of classes that we can try to instantiate. * Invokes the constructor of the non-abstract classes. Note that we need * to use a set of hints in which the "key" argument has been removed for * the same reasons than in super.getServiceProvider(...) method. This is * okay since we check the value of this hint explicitly in this method. * This apply to the block of code after the loop as well. */ hints = hints.clone(); if (hints.remove(key) != hint) { // Should never happen unless changed concurrently in an other thread. throw new AssertionError(hint); } for (int i=0; i<types.length; i++) { final Class<?> type = types[i]; if (type!=null && category.isAssignableFrom(type)) { if (unavailables.contains(type)) { // We already tried this factory before and failed. continue; } final int modifiers = type.getModifiers(); if (!Modifier.isAbstract(modifiers)) { final T candidate = createSafe(category, type, hints); if (candidate == null) { continue; } if (isAcceptable(candidate, category, hints, filter, true)) { cache(category, candidate); return candidate; } dispose(candidate); } } } } } /* * No implementation hint provided. Search the first implementation * accepting a Hints argument. No-args constructor will be ignored. * Note: all Factory objects should be fully constructed by now, * since the super-class has already iterated over all factories. */ for (final Iterator<T> it=getUnfilteredProviders(category); it.hasNext();) { final T factory = it.next(); final Class<?> implementationType = factory.getClass(); if (unavailables.contains(implementationType)) { // We already tried this factory before and failed. continue; } if (!Classes.isAssignableToAny(implementationType, types)) { continue; } if (filter!=null && !filter.filter(factory)) { continue; } final T candidate; try { candidate = createSafe(category, implementationType, hints); } catch (FactoryNotFoundException exception) { // The factory has a dependency which has not been found. // Be tolerant to that kind of error. Logging.recoverableException(LOGGER, DynamicFactoryRegistry.class, "getServiceProvider", exception); continue; } catch (FactoryRegistryException exception) { if (exception.getCause() instanceof NoSuchMethodException) { // No accessible constructor with the expected argument. // Try an other implementation. continue; } else { // Other kind of error, probably unexpected. // Let the exception propagates. throw exception; } } if (candidate == null) { continue; } if (isAcceptable(candidate, category, hints, filter, true)) { cache(category, candidate); return candidate; } dispose(candidate); } /* * Before to thrown the exception, check if a new cause is available. We may have a new * cause because all factories were available when we didn't asked for any hints, but * one of them failed when we tried to instantiate it with the user-supplied hints. It * may be for example because the user supplied a different JDBC connection. */ initCause(notFound); throw notFound; } /** * Invoked when a factory declares itelf as unavailable. This method is used * for remembering that it is not worth to try creating that factory again. * * @param factory The factory which declares itself as unavailable. * * @since 3.01 */ @Override final void unavailable(final Factory factory) { unavailables.add(factory.getClass()); super.unavailable(factory); } /** * If no instance was found in the method from the parent class, searches in the cache. * * @param category The category to look for. Usually an interface class. * @param implementationType The desired class for the implementation, or {@code null} if none. * @param filter An optional filter, or {@code null} if none. * @param hints A {@linkplain Hints map of hints}, or {@code null} if none. * @return A factory for the specified category and hints, or {@code null} if none. */ @Override final <T> T getServiceImplementation(final Class<T> category, final Class<?> implementationType, final Filter filter, final Hints hints) { T candidate = super.getServiceImplementation(category, implementationType, filter, hints); if (candidate != null) { return candidate; } final List<Reference<T>> cached = getCachedProviders(category); for (final Iterator<Reference<T>> it=cached.iterator(); it.hasNext();) { candidate = it.next().get(); if (candidate == null) { it.remove(); continue; } if (implementationType != null && !implementationType.isInstance(candidate)) { continue; } if (!isAcceptable(candidate, category, hints, filter, true)) { continue; } return candidate; } return null; } /** * Invokes {@link #createServiceProvider}, but checks against recursive calls. If the specified * implementation is already under construction, returns {@code null} in order to tell to * {@link #getServiceProvider} that it need to search for an other implementation. This is * needed for avoiding infinite recursivity when a factory is a wrapper around an ther factory * of the same category. For example this is the case of * {@link org.geotoolkit.referencing.operation.CachingCoordinateOperationFactory}. */ private <T> T createSafe(final Class<T> category, final Class<?> implementation, final Hints hints) { if (!underConstruction.add(implementation)) { return null; } try { return createServiceProvider(category, implementation, hints); } finally { if (!underConstruction.remove(implementation)) { throw new AssertionError(); } } } /** * Creates a new instance of the specified factory using the specified hints. * The default implementation tries to instantiate the given implementation class * using the first of the following constructors found: * <p> * <ul> * <li>Constructor with a single {@link Hints} argument.</li> * <li>No-argument constructor, except if the implementation class is already a registered * provider (in which case it has already been evaluated by {@link FactoryRegistry}).</li> * </ul> * * @param <T> The category to instantiate. * @param category The category to instantiate. * @param implementation The factory class to instantiate. * @param hints The implementation hints. * @return The factory. * @throws FactoryRegistryException if the factory creation failed. * * @level advanced */ protected <T> T createServiceProvider(final Class<T> category, final Class<?> implementation, final Hints hints) throws FactoryRegistryException { Throwable cause; try { Constructor<?> constructor; try { constructor = implementation.getConstructor(HINTS_ARGUMENT); return category.cast(constructor.newInstance(new Object[] {hints})); } catch (NoSuchMethodException exception) { // Constructor does not exist or is not accessible. // We will fallback on the no-argument constructor. cause = exception; } /* * No public constructor expecting a Hints argument. Search for a no-argument * constructor, provided that the registry doesn't already contains an object * of that class (otherwise it should have been found before this point). The * search for non-registered factory may happen if user's hints contain an * implementation class. */ if (super.getServiceProviderByClass(implementation) == null) try { constructor = implementation.getConstructor((Class<?>[]) null); return category.cast(constructor.newInstance((Object[]) null)); } catch (NoSuchMethodException exception) { /* * No accessible constructor. Do not store the cause; * we will retrown the previous exception instead. */ } } catch (InvocationTargetException exception) { /* * Exception in the invoked constructor. We will wrap the cause in a * FactoryRegistryException, discarting the InvocationTargetException. */ cause = exception.getCause(); if (cause instanceof FactoryRegistryException) { throw (FactoryRegistryException) cause; } } catch (ReflectiveOperationException exception) { /* * Constructor is not public or the class is abstract. This exception should never * happen since we asked for Class.getConstructor(...), which returns only public * constructors, and we checked for non-abstract class before to invoke this method. */ cause = exception; } throw new FactoryRegistryException(Errors.format( Errors.Keys.CantCreateFactoryForType_1, implementation), cause); } /** * Disposes the specified factory. This method is invoked when a factory has been created, * and then {@code DynamicFactoryRegistry} determined that the factory doesn't meet user's * requirements. */ private static void dispose(final Object factory) { if (factory instanceof Factory) { ((Factory) factory).dispose(false); } } /** * {@inheritDoc} */ @Override public void scanForPlugins() { cache.clear(); super.scanForPlugins(); } }