package com.google.inject.spi;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.inject.internal.InternalFlags.getIncludeStackTraceOption;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Module;
import com.google.inject.PrivateBinder;
import com.google.inject.PrivateModule;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
import com.google.inject.binder.AnnotatedElementBuilder;
import com.google.inject.internal.AbstractBindingBuilder;
import com.google.inject.internal.BindingBuilder;
import com.google.inject.internal.ConstantBindingBuilderImpl;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ExposureBuilder;
import com.google.inject.internal.InternalFlags.IncludeStackTraceOption;
import com.google.inject.internal.MoreTypes;
import com.google.inject.internal.PrivateElementsImpl;
import com.google.inject.internal.ProviderMethodsModule;
import com.google.inject.internal.util.SourceProvider;
import com.google.inject.internal.util.StackTraceElements;
import com.google.inject.matcher.Matcher;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class Elements {
private static final BindingTargetVisitor<Object, Object> GET_INSTANCE_VISITOR =
new DefaultBindingTargetVisitor<Object, Object>() {
@Override
public Object visit(InstanceBinding<?> binding) {
return binding.getInstance();
}
@Override
protected Object visitOther(Binding<?> binding) {
throw new IllegalArgumentException();
}
};
public static List<Element> getElements(Module... modules) {
return getElements(Stage.DEVELOPMENT, Arrays.asList(modules));
}
public static List<Element> getElements(Stage stage, Module... modules) {
return getElements(stage, Arrays.asList(modules));
}
public static List<Element> getElements(Iterable<? extends Module> modules) {
return getElements(Stage.DEVELOPMENT, modules);
}
public static List<Element> getElements(Stage stage, Iterable<? extends Module> modules) {
RecordingBinder binder = new RecordingBinder(stage);
for (Module module : modules) {
binder.install(module);
}
binder.scanForAnnotatedMethods();
for (RecordingBinder child : binder.privateBinders) {
child.scanForAnnotatedMethods();
}
StackTraceElements.clearCache();
return Collections.unmodifiableList(binder.elements);
}
private static class ElementsAsModule implements Module {
private final Iterable<? extends Element> elements;
ElementsAsModule(Iterable<? extends Element> elements) {
this.elements = elements;
}
@Override
public void configure(Binder binder) {
for (Element element : elements) {
element.applyTo(binder);
}
}
}
public static Module getModule(final Iterable<? extends Element> elements) {
return new ElementsAsModule(elements);
}
@SuppressWarnings("unchecked")
static <T> BindingTargetVisitor<T, T> getInstanceVisitor() {
return (BindingTargetVisitor<T, T>) GET_INSTANCE_VISITOR;
}
private static class ModuleInfo {
private final Binder binder;
private final ModuleSource moduleSource;
private final boolean skipScanning;
private ModuleInfo(Binder binder, ModuleSource moduleSource, boolean skipScanning) {
this.binder = binder;
this.moduleSource = moduleSource;
this.skipScanning = skipScanning;
}
}
private static class RecordingBinder implements Binder, PrivateBinder {
private final Stage stage;
private final Map<Module, ModuleInfo> modules;
private final List<Element> elements;
private final Object source;
private ModuleSource moduleSource = null;
private final SourceProvider sourceProvider;
private final Set<ModuleAnnotatedMethodScanner> scanners;
private final RecordingBinder parent;
private final PrivateElementsImpl privateElements;
private final List<RecordingBinder> privateBinders;
private RecordingBinder(Stage stage) {
this.stage = stage;
this.modules = Maps.newLinkedHashMap();
this.scanners = Sets.newLinkedHashSet();
this.elements = Lists.newArrayList();
this.source = null;
this.sourceProvider =
SourceProvider.DEFAULT_INSTANCE.plusSkippedClasses(
Elements.class,
RecordingBinder.class,
AbstractModule.class,
ConstantBindingBuilderImpl.class,
AbstractBindingBuilder.class,
BindingBuilder.class);
this.parent = null;
this.privateElements = null;
this.privateBinders = Lists.newArrayList();
}
private RecordingBinder(
RecordingBinder prototype, Object source, SourceProvider sourceProvider) {
checkArgument(source == null ^ sourceProvider == null);
this.stage = prototype.stage;
this.modules = prototype.modules;
this.elements = prototype.elements;
this.scanners = prototype.scanners;
this.source = source;
this.moduleSource = prototype.moduleSource;
this.sourceProvider = sourceProvider;
this.parent = prototype.parent;
this.privateElements = prototype.privateElements;
this.privateBinders = prototype.privateBinders;
}
private RecordingBinder(RecordingBinder parent, PrivateElementsImpl privateElements) {
this.stage = parent.stage;
this.modules = Maps.newLinkedHashMap();
this.scanners = Sets.newLinkedHashSet(parent.scanners);
this.elements = privateElements.getElementsMutable();
this.source = parent.source;
this.moduleSource = parent.moduleSource;
this.sourceProvider = parent.sourceProvider;
this.parent = parent;
this.privateElements = privateElements;
this.privateBinders = parent.privateBinders;
}
@Override
public void bindInterceptor(
Matcher<? super Class<?>> classMatcher,
Matcher<? super Method> methodMatcher,
org.aopalliance.intercept.MethodInterceptor... interceptors) {
elements.add(
new InterceptorBinding(getElementSource(), classMatcher, methodMatcher, interceptors));
}
@Override
public void bindScope(Class<? extends Annotation> annotationType, Scope scope) {
elements.add(new ScopeBinding(getElementSource(), annotationType, scope));
}
@Override
@SuppressWarnings("unchecked")
public void requestInjection(Object instance) {
requestInjection((TypeLiteral<Object>) TypeLiteral.get(instance.getClass()), instance);
}
@Override
public <T> void requestInjection(TypeLiteral<T> type, T instance) {
elements.add(
new InjectionRequest<T>(
getElementSource(), MoreTypes.canonicalizeForKey(type), instance));
}
@Override
public <T> MembersInjector<T> getMembersInjector(final TypeLiteral<T> typeLiteral) {
final MembersInjectorLookup<T> element =
new MembersInjectorLookup<T>(
getElementSource(), MoreTypes.canonicalizeForKey(typeLiteral));
elements.add(element);
return element.getMembersInjector();
}
@Override
public <T> MembersInjector<T> getMembersInjector(Class<T> type) {
return getMembersInjector(TypeLiteral.get(type));
}
@Override
public void bindListener(Matcher<? super TypeLiteral<?>> typeMatcher, TypeListener listener) {
elements.add(new TypeListenerBinding(getElementSource(), listener, typeMatcher));
}
@Override
public void bindListener(
Matcher<? super Binding<?>> bindingMatcher, ProvisionListener... listeners) {
elements.add(new ProvisionListenerBinding(getElementSource(), bindingMatcher, listeners));
}
@Override
public void requestStaticInjection(Class<?>... types) {
for (Class<?> type : types) {
elements.add(new StaticInjectionRequest(getElementSource(), type));
}
}
void scanForAnnotatedMethods() {
for (ModuleAnnotatedMethodScanner scanner : scanners) {
for (Map.Entry<Module, ModuleInfo> entry : Maps.newLinkedHashMap(modules).entrySet()) {
Module module = entry.getKey();
ModuleInfo info = entry.getValue();
if (info.skipScanning) {
continue;
}
moduleSource = entry.getValue().moduleSource;
try {
info.binder.install(ProviderMethodsModule.forModule(module, scanner));
} catch (RuntimeException e) {
Collection<Message> messages = Errors.getMessagesFromThrowable(e);
if (!messages.isEmpty()) {
elements.addAll(messages);
} else {
addError(e);
}
}
}
}
moduleSource = null;
}
@Override
public void install(Module module) {
if (!modules.containsKey(module)) {
RecordingBinder binder = this;
boolean unwrapModuleSource = false;
if (module instanceof ProviderMethodsModule) {
Object delegate = ((ProviderMethodsModule) module).getDelegateModule();
if (moduleSource == null
|| !moduleSource.getModuleClassName().equals(delegate.getClass().getName())) {
moduleSource = getModuleSource(delegate);
unwrapModuleSource = true;
}
} else {
moduleSource = getModuleSource(module);
unwrapModuleSource = true;
}
boolean skipScanning = false;
if (module instanceof PrivateModule) {
binder = (RecordingBinder) binder.newPrivateBinder();
binder.modules.put(module, new ModuleInfo(binder, moduleSource, false));
skipScanning = true;
}
modules.put(module, new ModuleInfo(binder, moduleSource, skipScanning));
try {
module.configure(binder);
} catch (RuntimeException e) {
Collection<Message> messages = Errors.getMessagesFromThrowable(e);
if (!messages.isEmpty()) {
elements.addAll(messages);
} else {
addError(e);
}
}
binder.install(ProviderMethodsModule.forModule(module));
if (unwrapModuleSource) {
moduleSource = moduleSource.getParent();
}
}
}
@Override
public Stage currentStage() {
return stage;
}
@Override
public void addError(String message, Object... arguments) {
elements.add(new Message(getElementSource(), Errors.format(message, arguments)));
}
@Override
public void addError(Throwable t) {
String message = "An exception was caught and reported. Message: " + t.getMessage();
elements.add(new Message(ImmutableList.of((Object) getElementSource()), message, t));
}
@Override
public void addError(Message message) {
elements.add(message);
}
@Override
public <T> AnnotatedBindingBuilder<T> bind(Key<T> key) {
BindingBuilder<T> builder =
new BindingBuilder<T>(this, elements, getElementSource(), MoreTypes.canonicalizeKey(key));
return builder;
}
@Override
public <T> AnnotatedBindingBuilder<T> bind(TypeLiteral<T> typeLiteral) {
return bind(Key.get(typeLiteral));
}
@Override
public <T> AnnotatedBindingBuilder<T> bind(Class<T> type) {
return bind(Key.get(type));
}
@Override
public AnnotatedConstantBindingBuilder bindConstant() {
return new ConstantBindingBuilderImpl<Void>(this, elements, getElementSource());
}
@Override
public <T> Provider<T> getProvider(final Key<T> key) {
return getProvider(Dependency.get(key));
}
@Override
public <T> Provider<T> getProvider(final Dependency<T> dependency) {
final ProviderLookup<T> element = new ProviderLookup<>(getElementSource(), dependency);
elements.add(element);
return element.getProvider();
}
@Override
public <T> Provider<T> getProvider(Class<T> type) {
return getProvider(Key.get(type));
}
@Override
public void convertToTypes(
Matcher<? super TypeLiteral<?>> typeMatcher, TypeConverter converter) {
elements.add(new TypeConverterBinding(getElementSource(), typeMatcher, converter));
}
@Override
public RecordingBinder withSource(final Object source) {
return source == this.source ? this : new RecordingBinder(this, source, null);
}
@Override
public RecordingBinder skipSources(Class... classesToSkip) {
if (source != null) {
return this;
}
SourceProvider newSourceProvider = sourceProvider.plusSkippedClasses(classesToSkip);
return new RecordingBinder(this, null, newSourceProvider);
}
@Override
public PrivateBinder newPrivateBinder() {
PrivateElementsImpl privateElements = new PrivateElementsImpl(getElementSource());
RecordingBinder binder = new RecordingBinder(this, privateElements);
privateBinders.add(binder);
elements.add(privateElements);
return binder;
}
@Override
public void disableCircularProxies() {
elements.add(new DisableCircularProxiesOption(getElementSource()));
}
@Override
public void requireExplicitBindings() {
elements.add(new RequireExplicitBindingsOption(getElementSource()));
}
@Override
public void requireAtInjectOnConstructors() {
elements.add(new RequireAtInjectOnConstructorsOption(getElementSource()));
}
@Override
public void requireExactBindingAnnotations() {
elements.add(new RequireExactBindingAnnotationsOption(getElementSource()));
}
@Override
public void scanModulesForAnnotatedMethods(ModuleAnnotatedMethodScanner scanner) {
scanners.add(scanner);
elements.add(new ModuleAnnotatedMethodScannerBinding(getElementSource(), scanner));
}
@Override
public void expose(Key<?> key) {
exposeInternal(key);
}
@Override
public AnnotatedElementBuilder expose(Class<?> type) {
return exposeInternal(Key.get(type));
}
@Override
public AnnotatedElementBuilder expose(TypeLiteral<?> type) {
return exposeInternal(Key.get(type));
}
private <T> AnnotatedElementBuilder exposeInternal(Key<T> key) {
if (privateElements == null) {
addError(
"Cannot expose %s on a standard binder. "
+ "Exposed bindings are only applicable to private binders.",
key);
return new AnnotatedElementBuilder() {
@Override
public void annotatedWith(Class<? extends Annotation> annotationType) {}
@Override
public void annotatedWith(Annotation annotation) {}
};
}
ExposureBuilder<T> builder =
new ExposureBuilder<T>(this, getElementSource(), MoreTypes.canonicalizeKey(key));
privateElements.addExposureBuilder(builder);
return builder;
}
private ModuleSource getModuleSource(Object module) {
StackTraceElement[] partialCallStack;
if (getIncludeStackTraceOption() == IncludeStackTraceOption.COMPLETE) {
partialCallStack = getPartialCallStack(new Throwable().getStackTrace());
} else {
partialCallStack = new StackTraceElement[0];
}
if (moduleSource == null) {
return new ModuleSource(module, partialCallStack);
}
return moduleSource.createChild(module, partialCallStack);
}
private ElementSource getElementSource() {
StackTraceElement[] callStack = null;
StackTraceElement[] partialCallStack = new StackTraceElement[0];
ElementSource originalSource = null;
Object declaringSource = source;
if (declaringSource instanceof ElementSource) {
originalSource = (ElementSource) declaringSource;
declaringSource = originalSource.getDeclaringSource();
}
IncludeStackTraceOption stackTraceOption = getIncludeStackTraceOption();
if (stackTraceOption == IncludeStackTraceOption.COMPLETE
|| (stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE
&& declaringSource == null)) {
callStack = new Throwable().getStackTrace();
}
if (stackTraceOption == IncludeStackTraceOption.COMPLETE) {
partialCallStack = getPartialCallStack(callStack);
}
if (declaringSource == null) {
if (stackTraceOption == IncludeStackTraceOption.COMPLETE
|| stackTraceOption == IncludeStackTraceOption.ONLY_FOR_DECLARING_SOURCE) {
declaringSource = sourceProvider.get(callStack);
} else {
declaringSource = sourceProvider.getFromClassNames(moduleSource.getModuleClassNames());
}
}
return new ElementSource(originalSource, declaringSource, moduleSource, partialCallStack);
}
private StackTraceElement[] getPartialCallStack(StackTraceElement[] callStack) {
int toSkip = 0;
if (moduleSource != null) {
toSkip = moduleSource.getStackTraceSize();
}
int chunkSize = callStack.length - toSkip - 1;
StackTraceElement[] partialCallStack = new StackTraceElement[chunkSize];
System.arraycopy(callStack, 1, partialCallStack, 0, chunkSize);
return partialCallStack;
}
@Override
public String toString() {
return "Binder";
}
}
}