/* * Copyright (C) 2014 The Dagger Authors. * * 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.validation; 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.base.Keys.isValidImplicitProvisionKey; import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; 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 com.google.errorprone.annotations.CanIgnoreReturnValue; import dagger.Component; import dagger.MembersInjector; import dagger.Provides; import dagger.internal.codegen.base.SourceFileGenerationException; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.Binding; import dagger.internal.codegen.binding.BindingFactory; import dagger.internal.codegen.binding.InjectBindingRegistry; import dagger.internal.codegen.binding.KeyFactory; import dagger.internal.codegen.binding.MembersInjectionBinding; import dagger.internal.codegen.binding.ProvisionBinding; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.model.Key; import java.util.ArrayDeque; import java.util.Deque; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.processing.Messager; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; /** * 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.). */ @Singleton final class InjectBindingRegistryImpl implements InjectBindingRegistry { private final DaggerElements elements; private final DaggerTypes types; private final Messager messager; private final InjectValidator injectValidator; private final InjectValidator injectValidatorWhenGeneratingCode; private final KeyFactory keyFactory; private final BindingFactory bindingFactory; private final CompilerOptions compilerOptions; final class BindingsCollection { private final Class factoryClass; private final Map bindingsByKey = Maps.newLinkedHashMap(); private final Deque bindingsRequiringGeneration = new ArrayDeque<>(); private final Set materializedBindingKeys = Sets.newLinkedHashSet(); BindingsCollection(Class factoryClass) { this.factoryClass = factoryClass; } void generateBindings(SourceFileGenerator generator) throws SourceFileGenerationException { for (B binding = bindingsRequiringGeneration.poll(); binding != null; binding = bindingsRequiringGeneration.poll()) { checkState(!binding.unresolved().isPresent()); if (injectValidatorWhenGeneratingCode.isValidType(binding.key().type())) { 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, boolean warnIfNotAlreadyGenerated) { tryToCacheBinding(binding); tryToGenerateBinding(binding, warnIfNotAlreadyGenerated); } /** * 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, boolean warnIfNotAlreadyGenerated) { if (shouldGenerateBinding(binding)) { bindingsRequiringGeneration.offer(binding); if (compilerOptions.warnIfInjectionFactoryNotGeneratedUpstream() && warnIfNotAlreadyGenerated) { messager.printMessage( Kind.NOTE, String.format( "Generating a %s for %s. " + "Prefer to run the dagger processor over that class instead.", factoryClass.getSimpleName(), types.erasure(binding.key().type()))); // erasure to strip from msgs. } } } /** Returns true if the binding needs to be generated. */ private boolean shouldGenerateBinding(B binding) { return !binding.unresolved().isPresent() && !materializedBindingKeys.contains(binding.key()) && !bindingsRequiringGeneration.contains(binding) && elements.getTypeElement(generatedClassNameForBinding(binding)) == null; } /** 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.unresolved().isPresent() || binding.bindingTypeElement().get().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<>(Provider.class); private final BindingsCollection membersInjectionBindings = new BindingsCollection<>(MembersInjector.class); @Inject InjectBindingRegistryImpl( DaggerElements elements, DaggerTypes types, Messager messager, InjectValidator injectValidator, KeyFactory keyFactory, BindingFactory bindingFactory, CompilerOptions compilerOptions) { this.elements = elements; this.types = types; this.messager = messager; this.injectValidator = injectValidator; this.injectValidatorWhenGeneratingCode = injectValidator.whenGeneratingCode(); this.keyFactory = keyFactory; this.bindingFactory = bindingFactory; this.compilerOptions = compilerOptions; } // TODO(dpb): make the SourceFileGenerators fields so they don't have to be passed in @Override public void generateSourcesForRequiredBindings( SourceFileGenerator factoryGenerator, SourceFileGenerator membersInjectorGenerator) throws SourceFileGenerationException { provisionBindings.generateBindings(factoryGenerator); membersInjectionBindings.generateBindings(membersInjectorGenerator); } /** * Registers the binding for generation and later lookup. If the binding is resolved, we also * attempt to register an unresolved version of it. */ private void registerBinding(ProvisionBinding binding, boolean warnIfNotAlreadyGenerated) { provisionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated); if (binding.unresolved().isPresent()) { provisionBindings.tryToGenerateBinding(binding.unresolved().get(), warnIfNotAlreadyGenerated); } } /** * Registers the binding for generation and later lookup. If the binding is resolved, we also * attempt to register an unresolved version of it. */ private void registerBinding(MembersInjectionBinding binding, boolean warnIfNotAlreadyGenerated) { /* * We generate MembersInjector classes for types with @Inject constructors only if they have any * injection sites. * * We generate MembersInjector classes for types without @Inject constructors only if they have * local (non-inherited) injection sites. * * Warn only when registering bindings post-hoc for those types. */ if (warnIfNotAlreadyGenerated) { boolean hasInjectConstructor = !(injectedConstructors(binding.membersInjectedType()).isEmpty() && assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()); warnIfNotAlreadyGenerated = hasInjectConstructor ? !binding.injectionSites().isEmpty() : binding.hasLocalInjectionSites(); } membersInjectionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated); if (binding.unresolved().isPresent()) { membersInjectionBindings.tryToGenerateBinding( binding.unresolved().get(), warnIfNotAlreadyGenerated); } } @Override public Optional tryRegisterConstructor(ExecutableElement constructorElement) { return tryRegisterConstructor(constructorElement, Optional.empty(), false); } @CanIgnoreReturnValue private Optional tryRegisterConstructor( ExecutableElement constructorElement, Optional resolvedType, boolean warnIfNotAlreadyGenerated) { TypeElement typeElement = MoreElements.asType(constructorElement.getEnclosingElement()); DeclaredType type = MoreTypes.asDeclared(typeElement.asType()); Key key = keyFactory.forInjectConstructorWithResolvedType(type); ProvisionBinding cachedBinding = provisionBindings.getBinding(key); if (cachedBinding != null) { return Optional.of(cachedBinding); } ValidationReport report = injectValidator.validateConstructor(constructorElement); report.printMessagesTo(messager); if (report.isClean()) { ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType); registerBinding(binding, warnIfNotAlreadyGenerated); if (!binding.injectionSites().isEmpty()) { tryRegisterMembersInjectedType(typeElement, resolvedType, warnIfNotAlreadyGenerated); } return Optional.of(binding); } return Optional.empty(); } @Override public Optional tryRegisterMembersInjectedType(TypeElement typeElement) { return tryRegisterMembersInjectedType(typeElement, Optional.empty(), false); } @CanIgnoreReturnValue private Optional tryRegisterMembersInjectedType( TypeElement typeElement, Optional resolvedType, boolean warnIfNotAlreadyGenerated) { DeclaredType type = MoreTypes.asDeclared(typeElement.asType()); Key key = keyFactory.forInjectConstructorWithResolvedType(type); MembersInjectionBinding cachedBinding = membersInjectionBindings.getBinding(key); if (cachedBinding != null) { return Optional.of(cachedBinding); } ValidationReport report = injectValidator.validateMembersInjectionType(typeElement); report.printMessagesTo(messager); if (report.isClean()) { MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType); registerBinding(binding, warnIfNotAlreadyGenerated); for (Optional supertype = types.nonObjectSuperclass(type); supertype.isPresent(); supertype = types.nonObjectSuperclass(supertype.get())) { getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get())); } return Optional.of(binding); } return Optional.empty(); } @CanIgnoreReturnValue @Override public Optional getOrFindProvisionBinding(Key key) { checkNotNull(key); if (!isValidImplicitProvisionKey(key, types)) { return Optional.empty(); } 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())); ImmutableSet injectConstructors = ImmutableSet.builder() .addAll(injectedConstructors(element)) .addAll(assistedInjectedConstructors(element)) .build(); switch (injectConstructors.size()) { case 0: // No constructor found. return Optional.empty(); case 1: return tryRegisterConstructor( Iterables.getOnlyElement(injectConstructors), Optional.of(key.type()), true); default: throw new IllegalStateException("Found multiple @Inject constructors: " + injectConstructors); } } @CanIgnoreReturnValue @Override public Optional getOrFindMembersInjectionBinding(Key key) { checkNotNull(key); // TODO(gak): is checking the kind enough? checkArgument(isValidMembersInjectionKey(key)); MembersInjectionBinding binding = membersInjectionBindings.getBinding(key); if (binding != null) { return Optional.of(binding); } Optional newBinding = tryRegisterMembersInjectedType( MoreTypes.asTypeElement(key.type()), Optional.of(key.type()), true); return newBinding; } @Override public Optional getOrFindMembersInjectorProvisionBinding(Key key) { if (!isValidMembersInjectionKey(key)) { return Optional.empty(); } Key membersInjectionKey = keyFactory.forMembersInjectedType(types.unwrapType(key.type())); return getOrFindMembersInjectionBinding(membersInjectionKey) .map(binding -> bindingFactory.membersInjectorBinding(key, binding)); } }