package com.netflix.governator.internal; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import com.google.inject.Binding; import com.google.inject.ImplementedBy; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.ProvidedBy; import com.google.inject.TypeLiteral; import com.google.inject.spi.ConstructorBinding; 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.ElementSource; import com.google.inject.spi.InjectionPoint; import com.google.inject.spi.InjectionRequest; import com.google.inject.spi.LinkedKeyBinding; import com.google.inject.spi.ProviderBinding; import com.google.inject.spi.ProviderKeyBinding; import com.google.inject.spi.ProviderLookup; import com.google.inject.spi.StaticInjectionRequest; import com.google.inject.spi.UntargettedBinding; final class ElementsEx { /** * @param elements List of elements * @return List all Module classes that were involved in setting up bindings for the list of Elements */ public static List<String> getAllSourceModules(List<Element> elements) { List<String> names = new ArrayList<>(); for (Element element : elements) { if (element.getSource().getClass().isAssignableFrom(ElementSource.class)) { ElementSource source = (ElementSource)element.getSource(); names.addAll(source.getModuleClassNames()); } } return names; } /** * Discover all unbound keys (excluding JIT enabled keys) that Guice will not be able to create. * This set of keys can then be fed into an autobinder * * @param elements * @return Set of interfaces or abstract classes for which there is no binding */ public static Set<Key<?>> getAllUnboundKeys(List<Element> elements) { final Set<Key<?>> boundKeys = new HashSet<>(); for (Element element : elements) { element.acceptVisitor(new DefaultElementVisitor<Void>() { public <T> Void visit(Binding<T> binding) { boundKeys.add(binding.getKey()); return null; } }); } final Set<Key<?>> foundKeys = new HashSet<>(); for (Element element : elements) { element.acceptVisitor(new DefaultElementVisitor<Void>() { @Override public <T> Void visit(Binding<T> binding) { binding.acceptTargetVisitor(new DefaultBindingTargetVisitor<T, Void>() { @Override public Void visit(ProviderKeyBinding<? extends T> binding) { addFoundKeys(getUnboundDirectDependencies(binding.getProviderKey().getTypeLiteral(), boundKeys)); return null; } @Override public Void visit(LinkedKeyBinding<? extends T> binding) { if (!boundKeys.contains(binding.getLinkedKey())) { addFoundKeys(getUnboundDirectDependencies(binding.getLinkedKey().getTypeLiteral(), boundKeys)); } return null; } @Override public Void visit(UntargettedBinding<? extends T> binding) { addFoundKeys(getUnboundDirectDependencies(binding.getKey().getTypeLiteral(), boundKeys)); return null; } @Override public Void visit(ConstructorBinding<? extends T> binding) { addFoundKeys(getUnboundDirectDependencies(binding.getInjectableMembers(), boundKeys)); addFoundKeys(getUnboundDirectDependencies(binding.getDependencies(), boundKeys)); return null; } @Override public Void visit(ProviderBinding<? extends T> binding) { addFoundKeys(getUnboundDirectDependencies(binding.getProvidedKey().getTypeLiteral(), boundKeys)); return null; } private void addFoundKeys(Set<Key<?>> keys) { foundKeys.addAll(keys); } }); return null; } /** * Visit a request to inject the instance fields and methods of an instance. */ @Override public Void visit(InjectionRequest<?> request) { for (InjectionPoint ip : request.getInjectionPoints()) { for (Dependency<?> dep : ip.getDependencies()) { foundKeys.addAll(getUnboundDirectDependencies(dep.getKey().getTypeLiteral(), boundKeys)); } } return null; } /** * Visit a request to inject the static fields and methods of type. */ @Override public Void visit(StaticInjectionRequest request) { for (InjectionPoint ip : request.getInjectionPoints()) { for (Dependency<?> dep : ip.getDependencies()) { foundKeys.addAll(getUnboundDirectDependencies(dep.getKey().getTypeLiteral(), boundKeys)); } } return null; } /** * Visit a lookup of the provider for a type. */ @Override public <T> Void visit(ProviderLookup<T> lookup) { foundKeys.add(lookup.getDependency().getKey()); return null; } }); } // Recursively look at the final list of unbound keys to further discover dependencies // and exclude keys that may be instantiated using the JIT for (Key<?> key : foundKeys) { discoverDependencies(key, boundKeys); } foundKeys.removeAll(boundKeys); foundKeys.remove(Key.get(Injector.class)); return foundKeys; } static void discoverDependencies(Key<?> key, Set<Key<?>> boundKeys) { if (boundKeys.contains(key)) { return; } Class<?> rawType = key.getTypeLiteral().getRawType(); if (rawType.isInterface() || Modifier.isAbstract(rawType.getModifiers())) { ImplementedBy implementedBy = rawType.getAnnotation(ImplementedBy.class); if (implementedBy != null) { boundKeys.add(key); discoverDependencies(key, boundKeys); return; } ProvidedBy providedBy = rawType.getAnnotation(ProvidedBy.class); if (providedBy != null) { boundKeys.add(key); discoverDependencies(key, boundKeys); return; } } else { boundKeys.add(key); for (Key<?> dep : getUnboundDirectDependencies(key.getTypeLiteral(), boundKeys)) { discoverDependencies(dep, boundKeys); } } } static Set<Key<?>> getUnboundDirectDependencies(TypeLiteral<?> type, Set<Key<?>> boundKeys) { if (type.getRawType().isInterface()) { return Collections.emptySet(); } Set<Key<?>> keys = new HashSet<>(); keys.addAll(getUnboundDirectDependencies(InjectionPoint.forConstructorOf(type), boundKeys)); keys.addAll(getUnboundDirectDependencies(InjectionPoint.forInstanceMethodsAndFields(type), boundKeys)); return keys; } static Set<Key<?>> getUnboundDirectDependencies(Set<Dependency<?>> dependencies, Set<Key<?>> boundKeys) { Set<Key<?>> unboundKeys = new HashSet<>(); for (Dependency<?> dep : dependencies) { for (Dependency<?> dep2 : dep.getInjectionPoint().getDependencies()) { if (!boundKeys.contains(dep2.getKey())) { unboundKeys.add(dep2.getKey()); } } } return unboundKeys; } static Set<Key<?>> getUnboundDirectDependencies(InjectionPoint ip, Set<Key<?>> boundKeys) { Set<Key<?>> unboundKeys = new HashSet<>(); for (Dependency<?> dep : ip.getDependencies()) { if (!boundKeys.contains(dep.getKey())) { unboundKeys.add(dep.getKey()); } } return unboundKeys; } static Set<Key<?>> getUnboundDirectDependencies(Collection<InjectionPoint> ips, Set<Key<?>> boundKeys) { Set<Key<?>> unboundKeys = new HashSet<>(); for (InjectionPoint ip : ips) { for (Dependency<?> dep : ip.getDependencies()) { if (!boundKeys.contains(dep.getKey())) { unboundKeys.add(dep.getKey()); } } } return unboundKeys; } }