package tc.oc.commons.core.inject;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Set;
import javax.inject.Provider;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.InjectionPoint;
import tc.oc.commons.core.reflect.ResolvableType;
import tc.oc.commons.core.reflect.TypeArgument;
import tc.oc.commons.core.reflect.Types;
import tc.oc.commons.core.stream.Collectors;
/**
* Generate and install an {@link InnerFactory}
* @param <O> Outer type
* @param <I> Inner type
*/
public class InnerFactoryManifest<O, I> extends KeyedManifest {
private final TypeLiteral<I> innerType;
private final TypeLiteral<O> outerType;
private final Key<InnerFactory<O, I>> factoryKey;
public static <I> InnerFactoryManifest<?, I> forInnerClass(Class<I> type) {
return forInnerClass(Key.get(type));
}
public static <I> InnerFactoryManifest<?, I> forInnerClass(TypeLiteral<I> type) {
return forInnerClass(Key.get(type));
}
public static <I> InnerFactoryManifest<?, I> forInnerClass(Key<I> key) {
final Class<?> outer = key.getTypeLiteral().getRawType().getEnclosingClass();
if(outer == null) {
throw new IllegalArgumentException(key + " is not an inner class");
}
return new InnerFactoryManifest(key, TypeLiteral.get(outer));
}
protected InnerFactoryManifest() {
this(null, null);
}
public InnerFactoryManifest(Key<I> innerKey, TypeLiteral<O> outerType) {
if(innerKey == null) {
innerKey = Key.get(new ResolvableType<I>(){}.in(getClass()));
}
this.innerType = innerKey.getTypeLiteral();
this.outerType = outerType != null ? outerType : new ResolvableType<O>(){}.in(getClass());
this.factoryKey = innerKey.ofType(new ResolvableType<InnerFactory<O, I>>(){}
.with(new TypeArgument<O>(this.outerType){},
new TypeArgument<I>(this.innerType){}));
}
@Override
protected Object manifestKey() {
return factoryKey;
}
@Override
protected void configure() {
final InjectionPoint point = InjectionPoint.forConstructorOf(innerType);
final Constructor<I> constructor = (Constructor<I>) point.getMember();
constructor.setAccessible(true);
if(point.getDependencies().isEmpty() || !Types.isAssignable(point.getDependencies().get(0).getKey().getTypeLiteral(), outerType)) {
addError("Expected %s to take %s as the first parameter of its injectable constructor", innerType, outerType);
return;
}
final Set<Dependency<?>> dependencies = point.getDependencies()
.stream()
.skip(1)
.collect(Collectors.toImmutableSet());
final List<Provider<?>> providers = dependencies.stream()
.map(dep -> getProvider(dep.getKey()))
.collect(Collectors.toImmutableList());
final MembersInjector<I> membersInjector = getMembersInjector(innerType);
class FactoryImpl implements InnerFactory<O, I>, HasDependencies {
@Override
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
public I create(O outer) {
final Object[] args = new Object[providers.size() + 1];
args[0] = outer;
for(int i = 0; i < providers.size(); i++) {
args[i + 1] = providers.get(i).get();
}
return Injection.wrappingExceptions(() -> {
final I instance = constructor.newInstance(args);
membersInjector.injectMembers(instance);
return instance;
});
}
}
bind(factoryKey).toInstance(new FactoryImpl());
}
}