/* * Copyright (C) 2015 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.binding; import static com.google.auto.common.MoreElements.asExecutable; import static com.google.auto.common.MoreElements.getPackage; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; import static com.google.common.collect.Iterables.transform; import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.binding.SourceFiles.classFileName; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerElements.getMethodDescriptor; import static dagger.internal.codegen.langmodel.DaggerElements.isAnnotationPresent; import static javax.lang.model.type.TypeKind.DECLARED; import static javax.lang.model.type.TypeKind.NONE; import static javax.lang.model.util.ElementFilter.methodsIn; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableSet; import com.google.common.graph.Traverser; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; import dagger.Provides; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.model.Key; import dagger.multibindings.Multibinds; import dagger.producers.Produces; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** Contains metadata that describes a module. */ @AutoValue public abstract class ModuleDescriptor { public abstract TypeElement moduleElement(); abstract ImmutableSet includedModules(); public abstract ImmutableSet bindings(); /** The multibinding declarations contained in this module. */ abstract ImmutableSet multibindingDeclarations(); /** The {@link Module#subcomponents() subcomponent declarations} contained in this module. */ abstract ImmutableSet subcomponentDeclarations(); /** The {@link Binds} method declarations that define delegate bindings. */ abstract ImmutableSet delegateDeclarations(); /** The {@link BindsOptionalOf} method declarations that define optional bindings. */ abstract ImmutableSet optionalDeclarations(); /** The kind of the module. */ public abstract ModuleKind kind(); /** Returns all of the bindings declared in this module. */ @Memoized public ImmutableSet allBindingDeclarations() { return ImmutableSet.builder() .addAll(bindings()) .addAll(delegateDeclarations()) .addAll(multibindingDeclarations()) .addAll(optionalDeclarations()) .addAll(subcomponentDeclarations()) .build(); } /** Returns the keys of all bindings declared by this module. */ ImmutableSet allBindingKeys() { return allBindingDeclarations().stream().map(BindingDeclaration::key).collect(toImmutableSet()); } /** A {@link ModuleDescriptor} factory. */ @Singleton public static final class Factory implements ClearableCache { private final DaggerElements elements; private final KotlinMetadataUtil metadataUtil; private final BindingFactory bindingFactory; private final MultibindingDeclaration.Factory multibindingDeclarationFactory; private final DelegateDeclaration.Factory bindingDelegateDeclarationFactory; private final SubcomponentDeclaration.Factory subcomponentDeclarationFactory; private final OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory; private final Map cache = new HashMap<>(); @Inject Factory( DaggerElements elements, KotlinMetadataUtil metadataUtil, BindingFactory bindingFactory, MultibindingDeclaration.Factory multibindingDeclarationFactory, DelegateDeclaration.Factory bindingDelegateDeclarationFactory, SubcomponentDeclaration.Factory subcomponentDeclarationFactory, OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory) { this.elements = elements; this.metadataUtil = metadataUtil; this.bindingFactory = bindingFactory; this.multibindingDeclarationFactory = multibindingDeclarationFactory; this.bindingDelegateDeclarationFactory = bindingDelegateDeclarationFactory; this.subcomponentDeclarationFactory = subcomponentDeclarationFactory; this.optionalBindingDeclarationFactory = optionalBindingDeclarationFactory; } public ModuleDescriptor create(TypeElement moduleElement) { return reentrantComputeIfAbsent(cache, moduleElement, this::createUncached); } public ModuleDescriptor createUncached(TypeElement moduleElement) { ImmutableSet.Builder bindings = ImmutableSet.builder(); ImmutableSet.Builder delegates = ImmutableSet.builder(); ImmutableSet.Builder multibindingDeclarations = ImmutableSet.builder(); ImmutableSet.Builder optionalDeclarations = ImmutableSet.builder(); for (ExecutableElement moduleMethod : methodsIn(elements.getAllMembers(moduleElement))) { if (isAnnotationPresent(moduleMethod, Provides.class)) { bindings.add(bindingFactory.providesMethodBinding(moduleMethod, moduleElement)); } if (isAnnotationPresent(moduleMethod, Produces.class)) { bindings.add(bindingFactory.producesMethodBinding(moduleMethod, moduleElement)); } if (isAnnotationPresent(moduleMethod, Binds.class)) { delegates.add(bindingDelegateDeclarationFactory.create(moduleMethod, moduleElement)); } if (isAnnotationPresent(moduleMethod, Multibinds.class)) { multibindingDeclarations.add( multibindingDeclarationFactory.forMultibindsMethod(moduleMethod, moduleElement)); } if (isAnnotationPresent(moduleMethod, BindsOptionalOf.class)) { optionalDeclarations.add( optionalBindingDeclarationFactory.forMethod(moduleMethod, moduleElement)); } } if (metadataUtil.hasEnclosedCompanionObject(moduleElement)) { collectCompanionModuleBindings(moduleElement, bindings); } return new AutoValue_ModuleDescriptor( moduleElement, ImmutableSet.copyOf(collectIncludedModules(new LinkedHashSet<>(), moduleElement)), bindings.build(), multibindingDeclarations.build(), subcomponentDeclarationFactory.forModule(moduleElement), delegates.build(), optionalDeclarations.build(), ModuleKind.forAnnotatedElement(moduleElement).get()); } private void collectCompanionModuleBindings( TypeElement moduleElement, ImmutableSet.Builder bindings) { checkArgument(metadataUtil.hasEnclosedCompanionObject(moduleElement)); TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(moduleElement); ImmutableSet bindingElementDescriptors = bindings.build().stream() .map(binding -> getMethodDescriptor(asExecutable(binding.bindingElement().get()))) .collect(toImmutableSet()); methodsIn(elements.getAllMembers(companionModule)).stream() // Binding methods in companion objects with @JvmStatic are mirrored in the enclosing // class, therefore we should ignore it or else it'll be a duplicate binding. .filter(method -> !KotlinMetadataUtil.isJvmStaticPresent(method)) // Fallback strategy for de-duping contributing bindings in the companion module with // @JvmStatic by comparing descriptors. Contributing bindings are the only valid bindings // a companion module can declare. See: https://youtrack.jetbrains.com/issue/KT-35104 // TODO(danysantiago): Checks qualifiers too. .filter(method -> !bindingElementDescriptors.contains(getMethodDescriptor(method))) .forEach( method -> { if (isAnnotationPresent(method, Provides.class)) { bindings.add(bindingFactory.providesMethodBinding(method, companionModule)); } if (isAnnotationPresent(method, Produces.class)) { bindings.add(bindingFactory.producesMethodBinding(method, companionModule)); } }); } /** Returns all the modules transitively included by given modules, including the arguments. */ ImmutableSet transitiveModules(Iterable modules) { return ImmutableSet.copyOf( Traverser.forGraph( (ModuleDescriptor module) -> transform(module.includedModules(), this::create)) .depthFirstPreOrder(transform(modules, this::create))); } @CanIgnoreReturnValue private Set collectIncludedModules( Set includedModules, TypeElement moduleElement) { TypeMirror superclass = moduleElement.getSuperclass(); if (!superclass.getKind().equals(NONE)) { verify(superclass.getKind().equals(DECLARED)); TypeElement superclassElement = MoreTypes.asTypeElement(superclass); if (!superclassElement.getQualifiedName().contentEquals(Object.class.getCanonicalName())) { collectIncludedModules(includedModules, superclassElement); } } moduleAnnotation(moduleElement) .ifPresent( moduleAnnotation -> { includedModules.addAll(moduleAnnotation.includes()); includedModules.addAll(implicitlyIncludedModules(moduleElement)); }); return includedModules; } // @ContributesAndroidInjector generates a module that is implicitly included in the enclosing // module private ImmutableSet implicitlyIncludedModules(TypeElement moduleElement) { TypeElement contributesAndroidInjector = elements.getTypeElement("dagger.android.ContributesAndroidInjector"); if (contributesAndroidInjector == null) { return ImmutableSet.of(); } return methodsIn(moduleElement.getEnclosedElements()).stream() .filter(method -> isAnnotationPresent(method, contributesAndroidInjector.asType())) .map(method -> elements.checkTypePresent(implicitlyIncludedModuleName(method))) .collect(toImmutableSet()); } private String implicitlyIncludedModuleName(ExecutableElement method) { return getPackage(method).getQualifiedName() + "." + classFileName(ClassName.get(MoreElements.asType(method.getEnclosingElement()))) + "_" + LOWER_CAMEL.to(UPPER_CAMEL, method.getSimpleName().toString()); } @Override public void clearCache() { cache.clear(); } } }