/* * Copyright 2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gradle.internal.service; import org.gradle.api.Action; import org.gradle.api.Nullable; import org.gradle.api.specs.Spec; import org.gradle.internal.Factory; import org.gradle.internal.concurrent.CompositeStoppable; import org.gradle.internal.concurrent.Stoppable; import org.gradle.internal.util.BiFunction; import java.io.Closeable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Formatter; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * A hierarchical {@link ServiceRegistry} implementation. * * <p>Subclasses can register services by:</p> * * <li>Calling {@link #add(Class, Object)} to register a service instance.</li> * * <li>Calling {@link #addProvider(Object)} to register a service provider bean. A provider bean may have factory, decorator and configuration methods as described below.</li> * * <li>Adding a factory method. A factory method should have a name that starts with 'create', and have a non-void return type. For example, <code>protected SomeService createSomeService() { .... * }</code>. Parameters are injected using services from this registry or its parents. Parameter of type {@link ServiceRegistry} will receive the service registry that owns the service. Parameter ot * type {@code List<T>} will receive all services of type T, if any. Note that factory methods with a single parameter and an return type equal to that parameter type are interpreted as decorator * methods.</li> * * <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single parameter, and a have return type equal to the parameter type. Before invoking the * method, the parameter is located in the parent service registry and then passed to the method.</li> * * <li>Adding a configure method. A configure method should be called 'configure', take a {@link ServiceRegistration} parameter, and a have a void return type. Additional parameters are injected using * services from this registry or its parents.</li> * * </ul> * * <p>Service instances are created on demand. {@link #getFactory(Class)} looks for a service instance which implements {@code Factory<T>} where {@code T} is the expected type.</p> * * <p>Service instances and factories are closed when the registry that created them is closed using {@link #close()}. If a service instance or factory implements {@link java.io.Closeable} or {@link * org.gradle.internal.concurrent.Stoppable} then the appropriate close() or stop() method is called. Instances are closed in reverse dependency order.</p> * * <p>Service registries are arranged in a hierarchy. If a service of a given type cannot be located, the registry uses its parent registry, if any, to locate the service.</p> */ public class DefaultServiceRegistry implements ServiceRegistry, Closeable { private final static ServiceRegistry[] NO_PARENTS = new ServiceRegistry[0]; private final static ServiceProvider[] NO_DEPENDENTS = new ServiceProvider[0]; private final static Object[] NO_PARAMS = new Object[0]; private static final ConcurrentMap<Type, BiFunction<ServiceProvider, LookupContext, Provider>> SERVICE_TYPE_PROVIDER_CACHE = new ConcurrentHashMap<Type, BiFunction<ServiceProvider, LookupContext, Provider>>(); private final Map<Type, ServiceProvider> providerCache = new IdentityHashMap<Type, ServiceProvider>(); private final Object lock = new Object(); private final OwnServices ownServices; private final Provider allServices; private final Provider parentServices; private final String displayName; private boolean closed; private boolean mutable = true; // access under lock private Provider asParentServicesProvider; public DefaultServiceRegistry() { this(null, NO_PARENTS); } public DefaultServiceRegistry(String displayName) { this(displayName, NO_PARENTS); } public DefaultServiceRegistry(ServiceRegistry... parents) { this(null, parents); } public DefaultServiceRegistry(String displayName, ServiceRegistry... parents) { this.displayName = displayName; this.ownServices = new OwnServices(); if (parents.length == 0) { this.parentServices = null; this.allServices = CachingProvider.of(ownServices); } else { parentServices = setupParentServices(parents); allServices = new CompositeProvider(CachingProvider.of(ownServices), parentServices); } findProviderMethods(this); } private static Provider setupParentServices(ServiceRegistry[] parents) { Provider parentServices; if (parents.length == 1) { parentServices = toParentServices(parents[0]); } else { Provider[] parentProviders = new Provider[parents.length]; for (int i = 0; i < parents.length; i++) { parentProviders[i] = toParentServices(parents[i]); } parentServices = new CompositeProvider(parentProviders); } return parentServices; } private Provider asProvider() { if (asParentServicesProvider == null) { asParentServicesProvider = CachingProvider.of(new ParentServices(this)); } return asParentServicesProvider; } private static Provider toParentServices(ServiceRegistry serviceRegistry) { if (serviceRegistry instanceof DefaultServiceRegistry) { return ((DefaultServiceRegistry) serviceRegistry).asProvider(); } return new ParentServices(serviceRegistry); } /** * Creates a service registry that uses the given providers. */ public static ServiceRegistry create(Object... providers) { DefaultServiceRegistry registry = new DefaultServiceRegistry(); for (Object provider : providers) { registry.addProvider(provider); } return registry; } private String getDisplayName() { return displayName == null ? getClass().getSimpleName() : displayName; } @Override public String toString() { return getDisplayName(); } private void findProviderMethods(Object target) { Class<?> type = target.getClass(); RelevantMethods methods = RelevantMethods.getMethods(type); for (ServiceMethod method : methods.decorators) { if (parentServices == null) { throw new ServiceLookupException(String.format("Cannot use decorator method %s.%s() when no parent registry is provided.", type.getSimpleName(), method.getName())); } ownServices.add(new DecoratorMethodService(target, method)); } for (ServiceMethod method : methods.factories) { ownServices.add(new FactoryMethodService(target, method)); } for (ServiceMethod method : methods.configurers) { applyConfigureMethod(method, target); } } private void applyConfigureMethod(ServiceMethod method, Object target) { Object[] params = new Object[method.getParameterTypes().length]; DefaultLookupContext context = new DefaultLookupContext(); for (int i = 0; i < method.getParameterTypes().length; i++) { Type paramType = method.getParameterTypes()[i]; if (paramType.equals(ServiceRegistration.class)) { params[i] = newRegistration(); } else { ServiceProvider paramProvider = context.find(paramType, allServices); if (paramProvider == null) { throw new ServiceLookupException(String.format("Cannot configure services using %s.%s() as required service of type %s is not available.", method.getOwner().getSimpleName(), method.getName(), format(paramType))); } params[i] = paramProvider.get(); } } try { method.invoke(target, params); } catch (Exception e) { throw new ServiceLookupException(String.format("Could not configure services using %s.%s().", method.getOwner().getSimpleName(), method.getName()), e); } } /** * Adds services to this container using the given action. */ public void register(Action<? super ServiceRegistration> action) { assertMutable(); action.execute(newRegistration()); } private void assertMutable() { if (!mutable) { throw new IllegalStateException("Cannot add provide to service registry " + this + " as it is no longer mutable"); } } private ServiceRegistration newRegistration() { return new ServiceRegistration() { public <T> void add(Class<T> serviceType, T serviceInstance) { DefaultServiceRegistry.this.add(serviceType, serviceInstance); } public void add(Class<?> serviceType) { ownServices.add(new ConstructorService(serviceType)); } public void addProvider(Object provider) { DefaultServiceRegistry.this.addProvider(provider); } }; } /** * Adds a service to this registry. The given object is closed when this registry is closed. */ public <T> DefaultServiceRegistry add(Class<T> serviceType, final T serviceInstance) { assertMutable(); ownServices.add(new FixedInstanceService<T>(serviceType, serviceInstance)); return this; } /** * Adds a service provider bean to this registry. This provider may define factory and decorator methods. */ public DefaultServiceRegistry addProvider(Object provider) { assertMutable(); findProviderMethods(provider); return this; } /** * Closes all services for this registry. For each service, if the service has a public void close() or stop() method, that method is called to close the service. */ public void close() { synchronized (lock) { try { CompositeStoppable.stoppable(allServices).stop(); } finally { closed = true; } } } public boolean isClosed() { return closed; } private static String format(Type type) { if (type instanceof Class) { Class<?> aClass = (Class) type; return aClass.getSimpleName(); } else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; StringBuilder builder = new StringBuilder(); builder.append(format(parameterizedType.getRawType())); builder.append("<"); for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) { Type typeParam = parameterizedType.getActualTypeArguments()[i]; if (i > 0) { builder.append(", "); } builder.append(format(typeParam)); } builder.append(">"); return builder.toString(); } return type.toString(); } public boolean hasService(Class<?> serviceType) { assertValidServiceType(serviceType); return allServices.hasService(serviceType); } public <T> List<T> getAll(Class<T> serviceType) throws ServiceLookupException { synchronized (lock) { mutable = false; if (closed) { throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.", format(serviceType), getDisplayName())); } if (!hasService(serviceType)) { return Collections.emptyList(); } List<ServiceProvider> providers = new ArrayList<ServiceProvider>(); DefaultLookupContext context = new DefaultLookupContext(); allServices.getAll(context, serviceType, providers); List<T> services = new ArrayList<T>(providers.size()); for (ServiceProvider provider : providers) { services.add(serviceType.cast(provider.get())); } return services; } } public <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException { return serviceType.cast(doGet(serviceType)); } public Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException { return doGet(serviceType); } private Object doGet(Type serviceType) throws IllegalArgumentException { synchronized (lock) { mutable = false; if (closed) { throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.", format(serviceType), getDisplayName())); } ServiceProvider provider = providerCache.get(serviceType); if (provider == null) { provider = getServiceProvider(serviceType); providerCache.put(serviceType, provider); } return provider.get(); } } private ServiceProvider getServiceProvider(Type serviceType) { Type lookupType = extractServiceType(serviceType); ServiceProvider provider = hasService(unwrap(lookupType)) ? new DefaultLookupContext().find(serviceType, allServices) : null; if (provider == null) { throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.", format(serviceType), getDisplayName())); } return provider; } private static Type extractServiceType(Type mayBeFactoryType) { Type serviceType = mayBeFactoryType; if (mayBeFactoryType instanceof ParameterizedType) { Type rawType = ((ParameterizedType) mayBeFactoryType).getRawType(); if (rawType == List.class) { serviceType = ((ParameterizedType) mayBeFactoryType).getActualTypeArguments()[0]; } } return serviceType; } public <T> Factory<T> getFactory(Class<T> type) { synchronized (lock) { if (closed) { throw new IllegalStateException(String.format("Cannot locate factory for objects of type %s, as %s has been closed.", format(type), getDisplayName())); } if (hasService(Factory.class)) { DefaultLookupContext context = new DefaultLookupContext(); ServiceProvider factory = allServices.getFactory(context, type); if (factory != null) { return (Factory<T>) factory.get(); } } throw new UnknownServiceException(type, String.format("No factory for objects of type %s available in %s.", format(type), getDisplayName())); } } public <T> T newInstance(Class<T> type) { return getFactory(type).create(); } /** * Provides a single service instance. */ interface ServiceProvider { String getDisplayName(); Object get(); void requiredBy(Provider provider); } /** * Provides a set or zero or more services. */ interface Provider extends Stoppable { /** * Locates a service instance of the given type. Returns null if this provider does not provide a service of this type. */ ServiceProvider getService(LookupContext context, TypeSpec serviceType); /** * Locates a factory for services of the given type. Returns null if this provider does not provide any services of this type. */ ServiceProvider getFactory(LookupContext context, Class<?> type); /** * Collects all services of the given type. */ void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result); boolean hasService(Class<?> type); } private class OwnServices implements Provider { private List<Provider> providers; private Set<Class<?>> serviceTypes; @Override public ServiceProvider getFactory(LookupContext context, Class<?> type) { if (!hasService(Factory.class)) { return null; } List<ServiceProvider> candidates = new ArrayList<ServiceProvider>(); for (Provider provider : providers) { ServiceProvider factory = provider.getFactory(context, type); if (factory != null) { candidates.add(factory); } } if (candidates.size() == 0) { return null; } if (candidates.size() == 1) { return candidates.get(0); } Set<String> descriptions = new TreeSet<String>(); for (ServiceProvider candidate : candidates) { descriptions.add(candidate.getDisplayName()); } Formatter formatter = new Formatter(); formatter.format("Multiple factories for objects of type %s available in %s:", format(type), getDisplayName()); for (String description : descriptions) { formatter.format("%n - %s", description); } throw new ServiceLookupException(formatter.toString()); } @Override public ServiceProvider getService(LookupContext context, TypeSpec serviceType) { if (!hasService(unwrap(serviceType.getType()))) { return null; } ServiceProvider singleCandidate = null; List<ServiceProvider> candidates = null; for (Provider provider : providers) { ServiceProvider service = provider.getService(context, serviceType); if (service != null) { if (singleCandidate == null) { singleCandidate = service; } else { if (candidates == null) { candidates = new ArrayList<ServiceProvider>(2); candidates.add(singleCandidate); } candidates.add(service); } } } if (candidates == null && singleCandidate == null) { return null; } if (candidates == null) { return singleCandidate; } Set<String> descriptions = new TreeSet<String>(); for (ServiceProvider candidate : candidates) { descriptions.add(candidate.getDisplayName()); } Formatter formatter = new Formatter(); formatter.format("Multiple services of type %s available in %s:", format(serviceType.getType()), getDisplayName()); for (String description : descriptions) { formatter.format("%n - %s", description); } throw new ServiceLookupException(formatter.toString()); } @Override public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) { if (!hasService(serviceType)) { return; } for (Provider provider : providers) { provider.getAll(context, serviceType, result); } } public boolean hasService(Class<?> serviceType) { return serviceTypes != null && serviceTypes.contains(serviceType); } @Override public void stop() { if (providers == null) { return; } CompositeStoppable.stoppable(providers).stop(); } public void add(Provider provider) { assertMutable(); if (providers == null) { providers = new ArrayList<Provider>(); serviceTypes = new HashSet<Class<?>>(); serviceTypes.add(Object.class); } this.providers.add(provider); if (provider instanceof SingletonService) { addServiceType(((SingletonService) provider).serviceClass); } else { throw new UnsupportedOperationException("Unsupported service provider type: " + provider); } } private void addServiceType(Class<?> serviceType) { if (serviceType!=null && serviceTypes.add(serviceType)) { addServiceType(serviceType.getSuperclass()); Class<?>[] interfaces = serviceType.getInterfaces(); if (interfaces!=null) { for (Class<?> intf : interfaces) { addServiceType(intf); } } } } } private static Class<?> unwrap(Type type) { if (type instanceof Class) { return (Class) type; } else { if (type instanceof WildcardType) { final WildcardType wildcardType = (WildcardType) type; if (wildcardType.getUpperBounds()[0] instanceof Class && wildcardType.getLowerBounds().length == 0) { return (Class<?>) wildcardType.getUpperBounds()[0]; } } ParameterizedType parameterizedType = (ParameterizedType) type; return (Class) parameterizedType.getRawType(); } } private static abstract class ManagedObjectProvider<T> implements Provider { private T instance; private Set<Provider> dependents; protected void setInstance(T instance) { this.instance = instance; } public T getInstance() { if (instance == null) { instance = create(); assert instance != null : String.format("create() of %s returned null", toString()); } return instance; } protected abstract T create(); public void requiredBy(Provider provider) { if (dependents == null) { dependents = new HashSet<Provider>(); } dependents.add(provider); } public void stop() { try { if (instance != null) { CompositeStoppable.stoppable(dependents == null ? Collections.emptyList() : dependents).add(instance).stop(); } } finally { if (dependents != null) { dependents.clear(); } instance = null; } } } private static abstract class SingletonService extends ManagedObjectProvider<Object> implements ServiceProvider { final Type serviceType; final Class serviceClass; // cached for performance Class factoryElementType; boolean bound; SingletonService(Type serviceType) { this.serviceType = serviceType; serviceClass = unwrap(serviceType); } @Override public String toString() { return getDisplayName(); } @Override public Object get() { return getInstance(); } private ServiceProvider prepare(LookupContext context) { if (!bound) { bind(context); bound = true; } return this; } protected void bind(LookupContext context) { } @Override public ServiceProvider getService(LookupContext context, TypeSpec serviceType) { if (!serviceType.isSatisfiedBy(this.serviceType)) { return null; } return prepare(context); } @Override public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) { if (serviceType.isAssignableFrom(this.serviceClass)) { result.add(prepare(context)); } } @Override public ServiceProvider getFactory(LookupContext context, Class<?> elementType) { if (!isFactory(serviceType, elementType)) { return null; } return prepare(context); } private boolean isFactory(Type type, Class<?> elementType) { Class c = unwrap(type); if (!Factory.class.isAssignableFrom(c)) { return false; } if (factoryElementType != null) { return elementType.isAssignableFrom(factoryElementType); } if (type instanceof ParameterizedType) { // Check if type is Factory<? extends ElementType> ParameterizedType parameterizedType = (ParameterizedType) type; if (parameterizedType.getRawType().equals(Factory.class)) { Type actualType = parameterizedType.getActualTypeArguments()[0]; if (actualType instanceof Class) { factoryElementType = (Class) actualType; return elementType.isAssignableFrom((Class<?>) actualType); } } } // Check if type extends Factory<? extends ElementType> for (Type interfaceType : c.getGenericInterfaces()) { if (isFactory(interfaceType, elementType)) { return true; } } return false; } @Override public boolean hasService(Class<?> type) { return type.isAssignableFrom(serviceClass); } } private abstract class FactoryService extends SingletonService { private ServiceProvider[] paramProviders; protected FactoryService(Type serviceType) { super(serviceType); } protected abstract Type[] getParameterTypes(); protected abstract Member getFactory(); @Override protected void bind(LookupContext context) { Type[] parameterTypes = getParameterTypes(); if (parameterTypes.length==0) { paramProviders = NO_DEPENDENTS; return; } paramProviders = new ServiceProvider[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { Type paramType = parameterTypes[i]; try { if (paramType.equals(ServiceRegistry.class)) { paramProviders[i] = getThisAsProvider(); } else { ServiceProvider paramProvider = context.find(paramType, allServices); if (paramProvider == null) { throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as required service of type %s is not available.", format(serviceType), getFactory().getDeclaringClass().getSimpleName(), getFactory().getName(), format(paramType))); } paramProviders[i] = paramProvider; paramProvider.requiredBy(this); } } catch (ServiceValidationException e) { throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as there is a problem with parameter #%s of type %s.", format(serviceType), getFactory().getDeclaringClass().getSimpleName(), getFactory().getName(), i + 1, format(paramType)), e); } } } @Override protected Object create() { Object[] params = assembleParameters(); Object result = invokeMethod(params); // Can discard the state required to create instance paramProviders = null; return result; } private Object[] assembleParameters() { if (paramProviders == NO_DEPENDENTS) { return NO_PARAMS; } Object[] params = new Object[paramProviders.length]; for (int i = 0; i < paramProviders.length; i++) { ServiceProvider paramProvider = paramProviders[i]; params[i] = paramProvider.get(); } return params; } protected abstract Object invokeMethod(Object[] params); } private class FactoryMethodService extends FactoryService { private final ServiceMethod method; private Object target; public FactoryMethodService(Object target, ServiceMethod method) { super(method.getServiceType()); this.target = target; this.method = method; } public String getDisplayName() { return "Service " + format(method.getServiceType()) + " at " + method.getOwner().getSimpleName() + "." + method.getName() + "()"; } protected Type[] getParameterTypes() { return method.getParameterTypes(); } @Override protected Member getFactory() { return method.getMethod(); } protected Object invokeMethod(Object[] params) { Object result; try { result = method.invoke(target, params); } catch (Exception e) { throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s().", format(serviceType), method.getOwner().getSimpleName(), method.getName()), e); } try { if (result == null) { throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s() as this method returned null.", format(serviceType), method.getOwner().getSimpleName(), method.getName())); } return result; } finally { // Can discard the state required to create instance target = null; } } } private ServiceProvider getThisAsProvider() { return new ServiceProvider() { public String getDisplayName() { return "ServiceRegistry " + DefaultServiceRegistry.this.getDisplayName(); } public Object get() { return DefaultServiceRegistry.this; } public void requiredBy(Provider provider) { } }; } private static class FixedInstanceService<T> extends SingletonService { public FixedInstanceService(Class<T> serviceType, T serviceInstance) { super(serviceType); setInstance(serviceInstance); } public String getDisplayName() { return "Service " + format(serviceType) + " with implementation " + getInstance(); } @Override protected Object create() { throw new UnsupportedOperationException(); } } private class ConstructorService extends FactoryService { private final Constructor<?> constructor; private ConstructorService(Class<?> serviceType) { super(serviceType); Constructor<?>[] constructors = serviceType.getDeclaredConstructors(); if (constructors.length != 1) { throw new ServiceValidationException(String.format("Expected a single constructor for %s.", format(serviceType))); } this.constructor = constructors[0]; } @Override protected Type[] getParameterTypes() { return constructor.getGenericParameterTypes(); } @Override protected Member getFactory() { return constructor; } @Override protected Object invokeMethod(Object[] params) { try { return constructor.newInstance(params); } catch (InvocationTargetException e) { throw new ServiceCreationException(String.format("Could not create service of type %s.", format(serviceType)), e.getCause()); } catch (Exception e) { throw new ServiceCreationException(String.format("Could not create service of type %s.", format(serviceType)), e); } } public String getDisplayName() { return "Service " + format(serviceType); } } private class DecoratorMethodService extends SingletonService { private final ServiceMethod method; private Object target; private ServiceProvider paramProvider; public DecoratorMethodService(Object target, ServiceMethod method) { super(method.getServiceType()); this.target = target; this.method = method; } public String getDisplayName() { return "Service " + format(method.getServiceType()) + " at " + method.getOwner().getSimpleName() + "." + method.getName() + "()"; } @Override protected void bind(LookupContext context) { Type paramType = method.getParameterTypes()[0]; DefaultLookupContext parentLookupContext = new DefaultLookupContext(); paramProvider = parentLookupContext.find(paramType, parentServices); if (paramProvider == null) { throw new ServiceCreationException(String.format("Cannot create service of type %s using %s.%s() as required service of type %s is not available in parent registries.", format(method.getServiceType()), method.getOwner().getSimpleName(), method.getName(), format(paramType))); } } @Override protected Object create() { Object param = paramProvider.get(); Object result; try { result = method.invoke(target, param); } catch (Exception e) { throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s().", format(method.getServiceType()), method.getOwner().getSimpleName(), method.getName()), e); } try { if (result == null) { throw new ServiceCreationException(String.format("Could not create service of type %s using %s.%s() as this method returned null.", format(method.getServiceType()), method.getOwner().getSimpleName(), method.getName())); } return result; } finally { // Can discard state required to create instance paramProvider = null; target = null; } } } private static class CachingProvider implements Provider { private static final Object ABSENT = new Object(); private final ConcurrentMap<Object, Object> seen = new ConcurrentHashMap<Object, Object>(); private final ConcurrentMap<Class<?>, List<ServiceProvider>> allServicesCache = new ConcurrentHashMap<Class<?>, List<ServiceProvider>>(); private final Map<Class<?>, Boolean> serviceTypes = new ConcurrentHashMap<Class<?>, Boolean>(); private final Provider delegate; private CachingProvider(Provider delegate) { this.delegate = delegate; } private static Provider of(Provider delegate) { if (delegate instanceof CachingProvider) { return delegate; } return new CachingProvider(delegate); } @Override public ServiceProvider getService(LookupContext context, TypeSpec serviceType) { Object cached = seen.get(serviceType); if (cached != null) { return cached == ABSENT ? null : (ServiceProvider) cached; } ServiceProvider service = delegate.getService(context, serviceType); return cacheServiceProvider(serviceType, service); } private ServiceProvider cacheServiceProvider(Object key, ServiceProvider service) { seen.putIfAbsent(key, service == null ? ABSENT : service); return service; } @Override public ServiceProvider getFactory(LookupContext context, Class<?> type) { Object cached = seen.get(type); if (cached != null) { return cached == ABSENT ? null : (ServiceProvider) cached; } ServiceProvider service = delegate.getFactory(context, type); return cacheServiceProvider(type, service); } @Override public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) { List<ServiceProvider> services = allServicesCache.get(serviceType); if (services != null) { result.addAll(services); return; } ArrayList<ServiceProvider> tmp = new ArrayList<ServiceProvider>(); delegate.getAll(context, serviceType, tmp); allServicesCache.putIfAbsent(serviceType, tmp); if (!tmp.isEmpty()) { result.addAll(tmp); } } @Override public boolean hasService(Class<?> type) { Boolean val = serviceTypes.get(type); if (val != null) { return val; } val = delegate.hasService(type); serviceTypes.put(type, val); return val; } @Override public void stop() { delegate.stop(); seen.clear(); allServicesCache.clear(); } } private static class CompositeProvider implements Provider { private final Provider[] providers; private CompositeProvider(Provider... providers) { this.providers = providers; } @Override public ServiceProvider getService(LookupContext context, TypeSpec serviceType) { if (hasService(unwrap(extractServiceType(serviceType.getType())))) { for (Provider provider : providers) { ServiceProvider service = provider.getService(context, serviceType); if (service != null) { return service; } } } return null; } @Override public ServiceProvider getFactory(LookupContext context, Class<?> type) { if (hasService(Factory.class)) { for (Provider provider : providers) { ServiceProvider factory = provider.getFactory(context, type); if (factory != null) { return factory; } } } return null; } @Override public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) { if (hasService(serviceType)) { for (Provider provider : providers) { provider.getAll(context, serviceType, result); } } } @Override public boolean hasService(Class<?> type) { for (Provider provider : providers) { if (provider.hasService(type)) { return true; } } return false; } @Override public void stop() { try { CompositeStoppable.stoppable(Arrays.asList(providers)).stop(); } finally { for (int i = 0; i < providers.length; i++) { providers[i] = null; } } } } private static class ParentServices implements Provider { private final ServiceRegistry parent; private ParentServices(ServiceRegistry parent) { this.parent = parent; } @Override public ServiceProvider getFactory(LookupContext context, Class<?> type) { try { Factory<?> factory = parent.getFactory(type); assert factory != null : String.format("parent returned null for factory type '%s'", type.getName()); return wrap(factory); } catch (UnknownServiceException e) { if (!e.getType().equals(type)) { throw e; } } return null; } @Override public ServiceProvider getService(LookupContext context, TypeSpec serviceType) { try { Object service = parent.get(serviceType.getType()); assert service != null : String.format("parent returned null for service type %s", format(serviceType.getType())); return wrap(service); } catch (UnknownServiceException e) { if (!e.getType().equals(serviceType.getType())) { throw e; } } return null; } private ServiceProvider wrap(final Object instance) { return new ServiceProvider() { public String getDisplayName() { return "ServiceRegistry " + parent; } public Object get() { return instance; } public void requiredBy(Provider provider) { // Ignore } }; } @Override public void getAll(LookupContext context, Class<?> serviceType, List<ServiceProvider> result) { List<?> services = parent.getAll(serviceType); assert services != null : String.format("parent returned null for services of type %s", format(serviceType)); for (Object service : services) { result.add(wrap(service)); } } @Override public boolean hasService(Class<?> type) { return parent.hasService(type); } @Override public void stop() { } } /** * Maintains state used while locating services and their dependencies. */ interface LookupContext { @Nullable ServiceProvider find(Type type, Provider provider); } interface TypeSpec extends Spec<Type> { Type getType(); } private static class ClassSpec implements TypeSpec { private final Class<?> type; private ClassSpec(Class<?> type) { this.type = type; } public Type getType() { return type; } public boolean isSatisfiedBy(Type element) { if (element instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) element; if (parameterizedType.getRawType() instanceof Class) { return type.isAssignableFrom((Class) parameterizedType.getRawType()); } } else if (element instanceof Class) { Class<?> other = (Class<?>) element; return type.isAssignableFrom(other); } return false; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ClassSpec classSpec = (ClassSpec) o; return type != null ? type.equals(classSpec.type) : classSpec.type == null; } @Override public int hashCode() { return type.hashCode(); } } private static class ParameterizedTypeSpec implements TypeSpec { private final Type type; private final TypeSpec rawType; private final List<TypeSpec> paramSpecs; private ParameterizedTypeSpec(Type type, TypeSpec rawType, List<TypeSpec> paramSpecs) { this.type = type; this.rawType = rawType; this.paramSpecs = paramSpecs; } public Type getType() { return type; } public boolean isSatisfiedBy(Type element) { if (element instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) element; if (!rawType.isSatisfiedBy(parameterizedType.getRawType())) { return false; } for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) { Type type = parameterizedType.getActualTypeArguments()[i]; if (!paramSpecs.get(i).isSatisfiedBy(type)) { return false; } } return true; } return false; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ParameterizedTypeSpec that = (ParameterizedTypeSpec) o; if (!type.equals(that.type)) { return false; } if (!rawType.equals(that.rawType)) { return false; } return paramSpecs.equals(that.paramSpecs); } @Override public int hashCode() { int result = type.hashCode(); result = 31 * result + rawType.hashCode(); result = 31 * result + paramSpecs.hashCode(); return result; } } private static class DefaultLookupContext implements LookupContext { private final Set<Type> visiting = new HashSet<Type>(); public ServiceProvider find(Type serviceType, Provider provider) { if (!visiting.add(serviceType)) { throw new ServiceValidationException(String.format("Cycle in dependencies of service of type %s.", format(serviceType))); } try { return getServiceProvider(serviceType, provider); } finally { visiting.remove(serviceType); } } public ServiceProvider getServiceProvider(Type serviceType, Provider provider) { BiFunction<ServiceProvider, LookupContext, Provider> function = SERVICE_TYPE_PROVIDER_CACHE.get(serviceType); if (function == null) { function = createServiceProviderFactory(serviceType); SERVICE_TYPE_PROVIDER_CACHE.putIfAbsent(serviceType, function); } return function.apply(this, provider); } private static BiFunction<ServiceProvider, LookupContext, Provider> createServiceProviderFactory(final Type serviceType) { if (serviceType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) serviceType; Type rawType = parameterizedType.getRawType(); if (rawType.equals(Factory.class)) { final Type typeArg = parameterizedType.getActualTypeArguments()[0]; if (typeArg instanceof Class) { return new BiFunction<ServiceProvider, LookupContext, Provider>() { @Override public ServiceProvider apply(LookupContext lookupContext, Provider provider) { return provider.getFactory(lookupContext, (Class) typeArg); } }; } if (typeArg instanceof WildcardType) { final WildcardType wildcardType = (WildcardType) typeArg; if (wildcardType.getLowerBounds().length == 1 && wildcardType.getUpperBounds().length == 1) { if (wildcardType.getLowerBounds()[0] instanceof Class && wildcardType.getUpperBounds()[0].equals(Object.class)) { return new BiFunction<ServiceProvider, LookupContext, Provider>() { @Override public ServiceProvider apply(LookupContext lookupContext, Provider provider) { return provider.getFactory(lookupContext, (Class<?>) wildcardType.getLowerBounds()[0]); } }; } } if (wildcardType.getLowerBounds().length == 0 && wildcardType.getUpperBounds().length == 1) { if (wildcardType.getUpperBounds()[0] instanceof Class) { return new BiFunction<ServiceProvider, LookupContext, Provider>() { @Override public ServiceProvider apply(LookupContext lookupContext, Provider provider) { return provider.getFactory(lookupContext, (Class<?>) wildcardType.getUpperBounds()[0]); } }; } } } } if (rawType instanceof Class && ((Class<?>) rawType).isAssignableFrom(List.class)) { Type typeArg = parameterizedType.getActualTypeArguments()[0]; if (typeArg instanceof Class) { return new ServicesWithTypeLookup((Class<?>) typeArg); } if (typeArg instanceof WildcardType) { WildcardType wildcardType = (WildcardType) typeArg; if (wildcardType.getUpperBounds()[0] instanceof Class && wildcardType.getLowerBounds().length == 0) { return new ServicesWithTypeLookup((Class<?>) wildcardType.getUpperBounds()[0]); } } } } final TypeSpec serviceTypeSpec = toSpec(serviceType); return new BiFunction<ServiceProvider, LookupContext, Provider>() { @Override public ServiceProvider apply(LookupContext lookupContext, Provider provider) { return provider.getService(lookupContext, serviceTypeSpec); } }; } static TypeSpec toSpec(Type serviceType) { if (serviceType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) serviceType; List<TypeSpec> paramSpecs = new ArrayList<TypeSpec>(); for (Type paramType : parameterizedType.getActualTypeArguments()) { paramSpecs.add(toSpec(paramType)); } return new ParameterizedTypeSpec(serviceType, toSpec(parameterizedType.getRawType()), paramSpecs); } else if (serviceType instanceof Class) { Class<?> serviceClass = (Class<?>) serviceType; assertValidServiceType(serviceClass); return new ClassSpec(serviceClass); } throw new ServiceValidationException(String.format("Locating services with type %s is not supported.", format(serviceType))); } private static class CollectionServiceProvider implements ServiceProvider { private final Type typeArg; private final List<Object> services; private final List<ServiceProvider> providers; public CollectionServiceProvider(Type typeArg, List<Object> services, List<ServiceProvider> providers) { this.typeArg = typeArg; this.services = services; this.providers = providers; } @Override public String getDisplayName() { return "services with type " + typeArg; } @Override public Object get() { return services; } @Override public void requiredBy(Provider provider) { for (ServiceProvider serviceProvider : providers) { serviceProvider.requiredBy(provider); } } } private static class ServicesWithTypeLookup implements BiFunction<ServiceProvider, LookupContext, Provider> { private final Class<?> elementClass; ServicesWithTypeLookup(Class<?> elementClass) { this.elementClass = elementClass; } @Override public ServiceProvider apply(final LookupContext lookupContext, final Provider provider) { List<ServiceProvider> providers = new ArrayList<ServiceProvider>(); provider.getAll(lookupContext, elementClass, providers); List<Object> services = new ArrayList<Object>(providers.size()); for (ServiceProvider serviceProvider : providers) { services.add(serviceProvider.get()); } return new CollectionServiceProvider(elementClass, services, providers); } } } private static void assertValidServiceType(Class<?> serviceClass) { if (serviceClass.isArray()) { throw new ServiceValidationException("Locating services with array type is not supported."); } if (serviceClass.isAnnotation()) { throw new ServiceValidationException("Locating services with annotation type is not supported."); } } }