/** * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.seed.core.internal.guice; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.assistedinject.FactoryModuleBuilder; import com.google.inject.util.Types; import java.lang.reflect.Type; import java.util.Collection; import java.util.Map; /** * GenericBindingStrategy resolves bindings for generic classes to implementations with unresolved type variables. * <p> * For instance it is possible to bind the following classes: * </p> * <pre> * class MyClass<I, J> { } * * class MyImplClass<I,J> extends MyClass { } * </pre> * For all the possible type variables (for instance for all the aggregate with their key). * <pre> * Collection<Class<?>[]> constructorParams = Lists.newArrayList(); * constructorParams.add(new Object[]{MyAggregate1.class, MyKey1.class}); * constructorParams.add(new Object[]{MyAggregate2.class, MyKey2.class}); * * GenericBindingStrategy bindingStrategy = new GenericBindingStrategy(MyClass.class, MyImplClass.class, constructorParams); * </pre> * This will allow to inject as follows: * <pre> * {@literal @}Inject * MyClass<MyAggregate1, MyKey1> mySuperClass; // inject instance of MyImplClass<MyAggregate1, MyKey1> * </pre> */ public class GenericBindingStrategy<T> implements BindingStrategy { /** * This class is the generic Guice assisted factory. */ private static final Class<?> DEFAULT_IMPL_FACTORY_CLASS = GenericGuiceFactory.class; private final Class<T> injecteeClass; private final Class<? extends T> genericImplClass; private Map<Type[], Key<?>> constructorParamsMap; private Collection<Type[]> constructorParams; /** * Constructors. * * @param injecteeClass the class to bind * @param genericImplClass the implementation to bind with unresolved constructorParams * @param constructorParams the collection of resolved constructorParams */ public GenericBindingStrategy(Class<T> injecteeClass, Class<? extends T> genericImplClass, Map<Type[], Key<?>> constructorParams) { this.constructorParamsMap = constructorParams; this.injecteeClass = injecteeClass; this.genericImplClass = genericImplClass; } /** * Constructors. * * @param injecteeClass the class to bind * @param genericImplClass the implementation to bind with unresolved constructorParams * @param constructorParams the collection of resolved constructorParams */ public GenericBindingStrategy(Class<T> injecteeClass, Class<? extends T> genericImplClass, Collection<Type[]> constructorParams) { this.constructorParams = constructorParams; this.injecteeClass = injecteeClass; this.genericImplClass = genericImplClass; } @Override public void resolve(Binder binder) { // Bind all the possible types for one class or interface. // For instance: Repository<Customer,String>, Repository<Order, Long>, etc. FactoryModuleBuilder guiceFactoryBuilder = new FactoryModuleBuilder(); if (constructorParamsMap != null) { for (Map.Entry<Type[], Key<?>> entry : constructorParamsMap.entrySet()) { bindKey(binder, guiceFactoryBuilder, entry.getKey(), entry.getValue()); } } else { for (Type[] params : constructorParams) { bindKey(binder, guiceFactoryBuilder, params, null); } } TypeLiteral<?> guiceAssistedFactory = TypeLiteral.get(Types.newParameterizedType(DEFAULT_IMPL_FACTORY_CLASS, genericImplClass)); binder.install(guiceFactoryBuilder.build(guiceAssistedFactory)); } @SuppressWarnings("unchecked") private void bindKey(Binder binder, FactoryModuleBuilder guiceFactoryBuilder, Type[] params, Key<?> defaultKey) { // If a default key is provided use a linked binding to bind it if (defaultKey != null) { binder.bind(defaultKey.getTypeLiteral()).to((Key) defaultKey); } // Get the key to bind Key<T> key = BindingUtils.resolveKey(injecteeClass, genericImplClass, params); // Prepare the Guice provider Provider<?> provider = new GenericGuiceProvider<T>(genericImplClass, params); binder.requestInjection(provider); binder.bind(key).toProvider((Provider) provider); // Prepare the factory for assisted injection guiceFactoryBuilder.implement(key, (Class) genericImplClass); } }