/*
* Copyright (C) 2014 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.internal.codegen;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dagger.Component;
import dagger.Provides;
import dagger.internal.codegen.writer.ClassName;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Messager;
import javax.inject.Inject;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
/**
* Maintains the collection of provision bindings from {@link Inject} constructors and members
* injection bindings from {@link Inject} fields and methods known to the annotation processor.
* Note that this registry does not handle any explicit bindings (those from {@link Provides}
* methods, {@link Component} dependencies, etc.).
*
* @author Gregory Kick
*/
final class InjectBindingRegistry {
private final Elements elements;
private final Types types;
private final Messager messager;
private final ProvisionBinding.Factory provisionBindingFactory;
private final MembersInjectionBinding.Factory membersInjectionBindingFactory;
final class BindingsCollection {
private final Map bindingsByKey = Maps.newLinkedHashMap();
private final Deque bindingsRequiringGeneration = new ArrayDeque<>();
private final Set materializedBindingKeys = Sets.newLinkedHashSet();
void generateBindings(SourceFileGenerator generator) throws SourceFileGenerationException {
for (B binding = bindingsRequiringGeneration.poll();
binding != null;
binding = bindingsRequiringGeneration.poll()) {
checkState(!binding.hasNonDefaultTypeParameters());
generator.generate(binding);
materializedBindingKeys.add(binding.key());
}
// Because Elements instantiated across processing rounds are not guaranteed to be equals() to
// the logically same element, clear the cache after generating
bindingsByKey.clear();
}
/** Returns a previously cached binding. */
B getBinding(Key key) {
return bindingsByKey.get(key);
}
/** Caches the binding and generates it if it needs generation. */
void tryRegisterBinding(B binding, ClassName factoryName, boolean explicit) {
tryToCacheBinding(binding);
tryToGenerateBinding(binding, factoryName, explicit);
}
/**
* Tries to generate a binding, not generating if it already is generated. For resolved
* bindings, this will try to generate the unresolved version of the binding.
*/
void tryToGenerateBinding(B binding, ClassName factoryName, boolean explicit) {
if (shouldGenerateBinding(binding, factoryName)) {
bindingsRequiringGeneration.offer(binding);
if (!explicit) {
messager.printMessage(Kind.NOTE, String.format(
"Generating a MembersInjector or Factory for %s. "
+ "Prefer to run the dagger processor over that class instead.",
types.erasure(binding.key().type()))); // erasure to strip from msgs.
}
}
}
/** Returns true if the binding needs to be generated. */
private boolean shouldGenerateBinding(B binding, ClassName factoryName) {
return !binding.hasNonDefaultTypeParameters()
&& elements.getTypeElement(factoryName.canonicalName()) == null
&& !materializedBindingKeys.contains(binding.key())
&& !bindingsRequiringGeneration.contains(binding);
}
/** Caches the binding for future lookups by key. */
private void tryToCacheBinding(B binding) {
// We only cache resolved bindings or unresolved bindings w/o type arguments.
// Unresolved bindings w/ type arguments aren't valid for the object graph.
if (binding.hasNonDefaultTypeParameters()
|| binding.bindingTypeElement().getTypeParameters().isEmpty()) {
Key key = binding.key();
Binding previousValue = bindingsByKey.put(key, binding);
checkState(previousValue == null || binding.equals(previousValue),
"couldn't register %s. %s was already registered for %s",
binding, previousValue, key);
}
}
}
private final BindingsCollection provisionBindings = new BindingsCollection<>();
private final BindingsCollection membersInjectionBindings =
new BindingsCollection<>();
InjectBindingRegistry(Elements elements,
Types types,
Messager messager,
ProvisionBinding.Factory provisionBindingFactory,
MembersInjectionBinding.Factory membersInjectionBindingFactory) {
this.elements = elements;
this.types = types;
this.messager = messager;
this.provisionBindingFactory = provisionBindingFactory;
this.membersInjectionBindingFactory = membersInjectionBindingFactory;
}
/**
* This method ensures that sources for all registered {@link Binding bindings} (either
* {@linkplain #registerBinding explicitly} or implicitly via
* {@link #getOrFindMembersInjectionBinding} or {@link #getOrFindProvisionBinding}) are generated.
*/
void generateSourcesForRequiredBindings(FactoryGenerator factoryGenerator,
MembersInjectorGenerator membersInjectorGenerator) throws SourceFileGenerationException {
provisionBindings.generateBindings(factoryGenerator);
membersInjectionBindings.generateBindings(membersInjectorGenerator);
}
ProvisionBinding registerBinding(ProvisionBinding binding) {
return registerBinding(binding, true);
}
MembersInjectionBinding registerBinding(MembersInjectionBinding binding) {
return registerBinding(binding, true);
}
/**
* Registers the binding for generation & later lookup. If the binding is resolved, we also
* attempt to register an unresolved version of it.
*/
private ProvisionBinding registerBinding(ProvisionBinding binding, boolean explicit) {
ClassName factoryName = generatedClassNameForBinding(binding);
provisionBindings.tryRegisterBinding(binding, factoryName, explicit);
if (binding.hasNonDefaultTypeParameters()) {
provisionBindings.tryToGenerateBinding(provisionBindingFactory.unresolve(binding),
factoryName, explicit);
}
return binding;
}
/**
* Registers the binding for generation & later lookup. If the binding is resolved, we also
* attempt to register an unresolved version of it.
*/
private MembersInjectionBinding registerBinding(
MembersInjectionBinding binding, boolean explicit) {
ClassName membersInjectorName = generatedClassNameForBinding(binding);
membersInjectionBindings.tryRegisterBinding(binding, membersInjectorName, explicit);
if (binding.hasNonDefaultTypeParameters()) {
membersInjectionBindings.tryToGenerateBinding(
membersInjectionBindingFactory.unresolve(binding), membersInjectorName, explicit);
}
return binding;
}
Optional getOrFindProvisionBinding(Key key) {
checkNotNull(key);
if (!key.isValidImplicitProvisionKey(types)) {
return Optional.absent();
}
ProvisionBinding binding = provisionBindings.getBinding(key);
if (binding != null) {
return Optional.of(binding);
}
// ok, let's see if we can find an @Inject constructor
TypeElement element = MoreElements.asType(types.asElement(key.type()));
List constructors =
ElementFilter.constructorsIn(element.getEnclosedElements());
ImmutableSet injectConstructors = FluentIterable.from(constructors)
.filter(new Predicate() {
@Override public boolean apply(ExecutableElement input) {
return isAnnotationPresent(input, Inject.class);
}
}).toSet();
switch (injectConstructors.size()) {
case 0:
// No constructor found.
return Optional.absent();
case 1:
ProvisionBinding constructorBinding = provisionBindingFactory.forInjectConstructor(
Iterables.getOnlyElement(injectConstructors), Optional.of(key.type()));
return Optional.of(registerBinding(constructorBinding, false));
default:
throw new IllegalStateException("Found multiple @Inject constructors: "
+ injectConstructors);
}
}
MembersInjectionBinding getOrFindMembersInjectionBinding(Key key) {
checkNotNull(key);
// TODO(gak): is checking the kind enough?
checkArgument(key.isValidMembersInjectionKey());
MembersInjectionBinding binding = membersInjectionBindings.getBinding(key);
if (binding != null) {
return binding;
}
return registerBinding(membersInjectionBindingFactory.forInjectedType(
MoreTypes.asDeclared(key.type()), Optional.of(key.type())), false);
}
}