package tc.oc.commons.core.inject; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.ParameterizedType; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import javax.inject.Provider; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.TypeToken; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.OutOfScopeException; import com.google.inject.ProvisionException; import com.google.inject.TypeLiteral; import com.google.inject.internal.Annotations; import com.google.inject.internal.Errors; import com.google.inject.internal.ProviderMethodsModule; import com.google.inject.spi.Dependency; import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.ModuleAnnotatedMethodScanner; import tc.oc.commons.core.reflect.FieldDelegate; import tc.oc.commons.core.reflect.Types; import tc.oc.commons.core.util.Optionals; import tc.oc.commons.core.util.ThrowingFunction; import tc.oc.commons.core.util.ThrowingRunnable; import tc.oc.commons.core.util.ThrowingSupplier; public class Injection { /** * Is the given type a {@link Provider} of some other type? Works for */ public static boolean isImplicitProvider(TypeLiteral<?> type) { return type.getType() instanceof ParameterizedType && Provider.class.isAssignableFrom(type.getRawType()); } /** * Return the type provided by the given {@link Provider} */ public static <T> TypeLiteral<T> providedType(TypeLiteral<? extends Provider<T>> providerType) { return (TypeLiteral<T>) TypeLiteral.get(((ParameterizedType) providerType.getType()).getActualTypeArguments()[0]); } /** * Return the type that is depended on by injecting the given type. * These types are the same, unless the injected type is a {@link Provider}, * in which case the dependency is on the provided type. */ public static TypeLiteral<?> dependencyType(TypeLiteral<?> injectedType) { if(isImplicitProvider(injectedType)) { return providedType((TypeLiteral<? extends Provider<Object>>) injectedType); } else { return injectedType; } } public static Key<?> dependencyKey(Key<?> key) { if(isImplicitProvider(key.getTypeLiteral())) { return Key.get(providedType((TypeLiteral<? extends Provider<Object>>) key.getTypeLiteral())); } else { return key; } } /** * Return all direct dependencies injected into the given type */ public static Stream<Dependency<?>> dependencies(Class<?> type) { return Stream.concat( Stream.of(InjectionPoint.forConstructorOf(type)), InjectionPoint.forInstanceMethodsAndFields(type).stream() ).flatMap(ip -> ip.getDependencies().stream()); } public static Optional<Class<? extends Annotation>> scopeAnnotation(Class<?> type) { return Optional.ofNullable(Annotations.findScopeAnnotation(new Errors(), type)); } public static RuntimeException wrapException(Throwable e) { if(e instanceof RuntimeException) { throw (RuntimeException) e; } else if(e instanceof Error) { throw (Error) e; } else { throw new ProvisionException("Provisioning error", e); } } public static <E extends Throwable> E unwrapException(Class<E> type, ProvisionException e) throws E { if(e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); } else if(type.isInstance(e.getCause())) { throw (E) e.getCause(); } else { throw e; } } public static <T, E extends Throwable> T unwrappingExceptions(Class<E> exception, Provider<T> block) throws E { try { return block.get(); } catch(ProvisionException e) { throw unwrapException(exception, e); } } public static <E extends Throwable> void unwrappingExceptions(Class<E> exception, ThrowingRunnable<E> block) throws E { try { block.runThrows(); } catch(ProvisionException e) { throw unwrapException(exception, e); } } public static <T, E extends Throwable> T unwrappingExceptions(Class<E> exception, ThrowingSupplier<T, E> block) throws E { try { return block.getThrows(); } catch(ProvisionException e) { throw unwrapException(exception, e); } } public static <E extends Throwable> ThrowingRunnable<E> unwrapExceptions(Class<E> exception, ThrowingRunnable<E> block) { return () -> unwrappingExceptions(exception, block); } public static <T, E extends Throwable> ThrowingSupplier<T, E> unwrapExceptions(Class<E> exception, ThrowingSupplier<T, E> block) { return () -> unwrappingExceptions(exception, block); } public static ThrowingRunnable<Throwable> unwrapExceptions(ThrowingRunnable<Throwable> block) { return () -> unwrappingExceptions(Throwable.class, block); } public static <T> ThrowingSupplier<T, Throwable> unwrapExceptions(ThrowingSupplier<T, Throwable> block) { return () -> unwrappingExceptions(Throwable.class, block); } public static <T> T wrappingExceptions(ThrowingSupplier<T, Throwable> block) { try { return block.getThrows(); } catch(Throwable e) { throw wrapException(e); } } public static void wrappingExceptions(ThrowingRunnable<Throwable> block) { try { block.runThrows(); } catch(Throwable e) { throw wrapException(e); } } public static <T, R> Function<T, R> wrappingExceptions(ThrowingFunction<T, R, Throwable> block) { return x -> { try { return block.apply(x); } catch(Throwable e) { throw wrapException(e); } }; } public static <T> Optional<T> getIfInScope(Provider<T> provider) { try { return Optional.of(provider.get()); } catch(ProvisionException e) { if(e.getCause() instanceof OutOfScopeException) { // This is what actually happens with Guice providers return Optional.empty(); } throw e; } catch(OutOfScopeException e) { // Handle this just in case return Optional.empty(); } } public static Stream<Binding<?>> bindings(Injector injector) { return injector == null ? Stream.empty() : Stream.concat(injector.getBindings().values().stream(), bindings(injector.getParent())); } public static <T> Stream<Binding<? extends T>> bindingsAssignableTo(Injector injector, TypeLiteral<T> baseType) { return (Stream) bindings(injector).filter(binding -> Types.isAssignable(baseType, binding.getKey().getTypeLiteral())); } public static <T> Stream<Key<? extends T>> keysAssignableTo(Injector injector, TypeLiteral<T> baseType) { return bindingsAssignableTo(injector, baseType).map(Binding::getKey); } public static <T> Stream<TypeLiteral<? extends T>> keyTypesAssignableTo(Injector injector, TypeLiteral<T> baseType) { return keysAssignableTo(injector, baseType).map(Key::getTypeLiteral); } public static void forEachBinding(Injector injector, Consumer<? super Binding<?>> consumer) { while(injector != null) { injector.getBindings().forEach((key, binding) -> consumer.accept(binding)); injector = injector.getParent(); } } public static <T> Map<Key<? extends T>, Binding<? extends T>> bindingsAssignableTo(Injector injector, Class<T> type) { return bindingsAssignableTo(injector, TypeToken.of(type)); } public static <T> Map<Key<? extends T>, Binding<? extends T>> bindingsAssignableTo(Injector injector, TypeToken<T> type) { final ImmutableMap.Builder<Key<? extends T>, Binding<? extends T>> builder = ImmutableMap.builder(); forEachBinding(injector, binding -> { if(type.isAssignableFrom(binding.getKey().getTypeLiteral().getType())) { builder.put((Key<? extends T>) binding.getKey(), (Binding<? extends T>) binding); } }); return builder.build(); } /** * HACK! * * Scan the given module for methods annotated with {@link ProvidesGeneric} and generate provider methods * from them. This works exactly like Guice's built-in provider methods, except that the owner type can * be specified explicitly, allowing for generic provider methods. * * HACK: Reflectively change the module type in ProviderMethodsModule, before it scans for provider methods. * This allows the actual type of a generic module to be provided at runtime. */ public static <T extends Module> Module providerMethodsModule(T hostModule, TypeLiteral<T> type) { final ProviderMethodsModule module = (ProviderMethodsModule) ProviderMethodsModule.forModule(hostModule, SCANNER); ProviderMethodsModule_typeLiteral.set(module, type); return module; } private static final FieldDelegate.Instance<ProviderMethodsModule, TypeLiteral<?>> ProviderMethodsModule_typeLiteral = FieldDelegate.Instance.forField(ProviderMethodsModule.class, new TypeToken<TypeLiteral<?>>(){}, "typeLiteral"); private static final ModuleAnnotatedMethodScanner SCANNER = new ModuleAnnotatedMethodScanner() { @Override public Set<? extends Class<? extends Annotation>> annotationClasses() { return ImmutableSet.of(ProvidesGeneric.class); } @Override public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key, InjectionPoint injectionPoint) { return key; } }; public static <T> Constructor<T> injectableConstructor(TypeLiteral<T> type) { return (Constructor<T>) InjectionPoint.forConstructorOf(type).getMember(); } public static <T> Constructor<T> injectableConstructor(Class<T> type) { return injectableConstructor(TypeLiteral.get(type)); } }