package tc.oc.commons.core.inject; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.Stage; import com.google.inject.TypeLiteral; import com.google.inject.spi.ConstructorBinding; import com.google.inject.spi.ConvertedConstantBinding; import com.google.inject.spi.DefaultBindingTargetVisitor; import com.google.inject.spi.DefaultElementVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.InjectionRequest; import com.google.inject.spi.InstanceBinding; import com.google.inject.spi.LinkedKeyBinding; import com.google.inject.spi.PrivateElements; import com.google.inject.spi.ProviderBinding; import com.google.inject.spi.ProviderInstanceBinding; import com.google.inject.spi.ProviderKeyBinding; import com.google.inject.spi.ProviderLookup; import com.google.inject.spi.StaticInjectionRequest; import com.google.inject.spi.UntargettedBinding; /** * Debugging tool that finds all dependencies for a set of modules, even implicit (JIT) ones. */ public class DependencyCollector { private static final Set<Class<?>> BLACKLIST = ImmutableSet.of(Injector.class, Logger.class); private final Set<Key<?>> requiredKeys = new HashSet<>(); private final Set<Key<?>> implicitBindings = new HashSet<>(); private final Map<Key<?>, Binding<?>> explicitBindings = new HashMap<>(); private final SetMultimap<Key<?>, Dependency<?>> dependenciesByKey = HashMultimap.create(); private final SetMultimap<InjectionPoint, Dependency<?>> dependenciesByInjectionPoint = HashMultimap.create(); private final SetMultimap<TypeLiteral<?>, InjectionPoint> injectionPointsByType = HashMultimap.create(); public Set<Key<?>> implicitBindings() { return implicitBindings; } public Map<Key<?>, Binding<?>> explicitBindings() {return explicitBindings; } public SetMultimap<Key<?>, Dependency<?>> dependenciesByKey() { return dependenciesByKey; } public SetMultimap<InjectionPoint, Dependency<?>> dependenciesByInjectionPoint() { return dependenciesByInjectionPoint; } public SetMultimap<TypeLiteral<?>, InjectionPoint> injectionPointsByType() { return injectionPointsByType; } public static void log(Logger logger, Level level, Iterable<? extends Module> modules) { new DependencyCollector().process(modules) .log(logger, level); } public DependencyCollector log(Logger logger, Level level) { logger.log(level, "Dumping all dependencies:"); for(Map.Entry<TypeLiteral<?>, Collection<InjectionPoint>> entry : injectionPointsByType().asMap().entrySet()) { logger.log(level, entry.getKey().toString()); for(InjectionPoint ip : entry.getValue()) { logger.log(level, " " + ip.getMember()); for(Dependency<?> dep : dependenciesByInjectionPoint().get(ip)) { logger.log(level, " " + dep); } } } return this; } public DependencyCollector clear() { requiredKeys.clear(); implicitBindings.clear(); explicitBindings.clear(); return this; } public DependencyCollector processElements(Iterable<Element> elements) { final ElementVisitor visitor = new ElementVisitor(); for(Element element : elements) { element.acceptVisitor(visitor); } processImplicitBindings(); return this; } public DependencyCollector process(Module... modules) { processElements(Elements.getElements(modules)); return this; } public DependencyCollector process(Iterable<? extends Module> modules) { processElements(Elements.getElements(Stage.TOOL, modules)); return this; } private boolean requireKey(Key<?> key) { if(BLACKLIST.contains(key.getTypeLiteral().getRawType())) return false; return requiredKeys.add(Injection.dependencyKey(key)); } private void processDependency(Dependency<?> dependency) { dependenciesByKey.put(dependency.getKey(), dependency); dependenciesByInjectionPoint.put(dependency.getInjectionPoint(), dependency); requireKey(dependency.getKey()); } private void processInjectionPoint(InjectionPoint injectionPoint) { injectionPointsByType.put(injectionPoint.getDeclaringType(), injectionPoint); injectionPoint.getDependencies().forEach(this::processDependency); } private void processInjectionPoints(Iterable<InjectionPoint> injectionPoint) { injectionPoint.forEach(this::processInjectionPoint); } private void processInstanceInjections(TypeLiteral<?> type) { InjectionPoint.forInstanceMethodsAndFields(type).forEach(this::processInjectionPoint); } private void processInjections(TypeLiteral<?> type) { processInjectionPoint(InjectionPoint.forConstructorOf(type)); processInstanceInjections(type); } private void processImplicitBindings() { for(;;) { ImmutableSet<Key<?>> keys = ImmutableSet.copyOf(Sets.difference(requiredKeys, Sets.union(explicitBindings.keySet(), implicitBindings))); if(keys.isEmpty()) break; for(Key<?> key : keys) { if(implicitBindings.add(key)) { processInjections(key.getTypeLiteral()); } } } } private class ElementVisitor extends DefaultElementVisitor<Object> { @Override public <T> Object visit(Binding<T> binding) { requireKey(binding.getKey()); explicitBindings.put(binding.getKey(), binding); binding.acceptTargetVisitor(new BindingVisitor<>()); return super.visit(binding); } @Override public <T> Object visit(ProviderLookup<T> providerLookup) { processDependency(providerLookup.getDependency()); return super.visit(providerLookup); } @Override public Object visit(InjectionRequest<?> injectionRequest) { processInjectionPoints(injectionRequest.getInjectionPoints()); return super.visit(injectionRequest); } @Override public Object visit(StaticInjectionRequest staticInjectionRequest) { processInjectionPoints(staticInjectionRequest.getInjectionPoints()); return super.visit(staticInjectionRequest); } @Override public Object visit(PrivateElements privateElements) { processElements(privateElements.getElements()); return super.visit(privateElements); } } private class BindingVisitor<T> extends DefaultBindingTargetVisitor<T, Object> { @Override public Object visit(InstanceBinding<? extends T> instanceBinding) { processInjectionPoints(instanceBinding.getInjectionPoints()); return super.visit(instanceBinding); } @Override public Object visit(ProviderInstanceBinding<? extends T> providerInstanceBinding) { processInjectionPoints(providerInstanceBinding.getInjectionPoints()); return super.visit(providerInstanceBinding); } @Override public Object visit(ProviderKeyBinding<? extends T> providerKeyBinding) { requireKey(providerKeyBinding.getProviderKey()); return super.visit(providerKeyBinding); } @Override public Object visit(LinkedKeyBinding<? extends T> linkedKeyBinding) { requireKey(linkedKeyBinding.getLinkedKey()); return super.visit(linkedKeyBinding); } @Override public Object visit(UntargettedBinding<? extends T> untargettedBinding) { processInjections(untargettedBinding.getKey().getTypeLiteral()); return super.visit(untargettedBinding); } @Override public Object visit(ConstructorBinding<? extends T> constructorBinding) { processInjectionPoint(constructorBinding.getConstructor()); processInjectionPoints(constructorBinding.getInjectableMembers()); return super.visit(constructorBinding); } @Override public Object visit(ProviderBinding<? extends T> providerBinding) { requireKey(providerBinding.getProvidedKey()); return super.visit(providerBinding); } @Override public Object visit(ConvertedConstantBinding<? extends T> convertedConstantBinding) { // TODO: What do I do here?? return super.visit(convertedConstantBinding); } } }