/* * 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 androidx.room.compiler.processing.compat.XConverters.toJavac; import static androidx.room.compiler.processing.compat.XConverters.toXProcessing; import static com.google.auto.common.MoreElements.asExecutable; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.google.common.base.Verify.verify; import static com.google.common.collect.Collections2.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.DaggerCollectors.toOptional; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerElements.getMethodDescriptor; import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; import static javax.lang.model.util.ElementFilter.methodsIn; import androidx.room.compiler.processing.XElementKt; import androidx.room.compiler.processing.XMethodElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; 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.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.DaggerSuperficialValidation; import dagger.internal.codegen.base.ModuleKind; import dagger.internal.codegen.javapoet.TypeNames; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.xprocessing.XElements; import dagger.spi.model.Key; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; /** Contains metadata that describes a module. */ @AutoValue public abstract class ModuleDescriptor { public abstract XTypeElement moduleElement(); 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 XProcessingEnv processingEnv; private final DaggerElements elements; 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 DaggerSuperficialValidation superficialValidation; private final Map cache = new HashMap<>(); @Inject Factory( XProcessingEnv processingEnv, DaggerElements elements, BindingFactory bindingFactory, MultibindingDeclaration.Factory multibindingDeclarationFactory, DelegateDeclaration.Factory bindingDelegateDeclarationFactory, SubcomponentDeclaration.Factory subcomponentDeclarationFactory, OptionalBindingDeclaration.Factory optionalBindingDeclarationFactory, DaggerSuperficialValidation superficialValidation) { this.processingEnv = processingEnv; this.elements = elements; this.bindingFactory = bindingFactory; this.multibindingDeclarationFactory = multibindingDeclarationFactory; this.bindingDelegateDeclarationFactory = bindingDelegateDeclarationFactory; this.subcomponentDeclarationFactory = subcomponentDeclarationFactory; this.optionalBindingDeclarationFactory = optionalBindingDeclarationFactory; this.superficialValidation = superficialValidation; } public ModuleDescriptor create(XTypeElement moduleElement) { return reentrantComputeIfAbsent(cache, moduleElement, this::createUncached); } public ModuleDescriptor createUncached(XTypeElement moduleElement) { ImmutableSet.Builder bindings = ImmutableSet.builder(); ImmutableSet.Builder delegates = ImmutableSet.builder(); ImmutableSet.Builder multibindingDeclarations = ImmutableSet.builder(); ImmutableSet.Builder optionalDeclarations = ImmutableSet.builder(); methodsIn(elements.getAllMembers(toJavac(moduleElement))).stream() .map(method -> toXProcessing(method, processingEnv)) .filter(XElementKt::isMethod) .map(XElements::asMethod) .forEach( moduleMethod -> { if (moduleMethod.hasAnnotation(TypeNames.PROVIDES)) { bindings.add(bindingFactory.providesMethodBinding(moduleMethod, moduleElement)); } if (moduleMethod.hasAnnotation(TypeNames.PRODUCES)) { bindings.add(bindingFactory.producesMethodBinding(moduleMethod, moduleElement)); } if (moduleMethod.hasAnnotation(TypeNames.BINDS)) { delegates.add( bindingDelegateDeclarationFactory.create(moduleMethod, moduleElement)); } if (moduleMethod.hasAnnotation(TypeNames.MULTIBINDS)) { multibindingDeclarations.add( multibindingDeclarationFactory.forMultibindsMethod( moduleMethod, moduleElement)); } if (moduleMethod.hasAnnotation(TypeNames.BINDS_OPTIONAL_OF)) { optionalDeclarations.add( optionalBindingDeclarationFactory.forMethod(moduleMethod, moduleElement)); } }); moduleElement.getEnclosedTypeElements().stream() .filter(XTypeElement::isCompanionObject) .collect(toOptional()) .ifPresent(companionModule -> collectCompanionModuleBindings(companionModule, bindings)); return new AutoValue_ModuleDescriptor( moduleElement, bindings.build(), multibindingDeclarations.build(), subcomponentDeclarationFactory.forModule(moduleElement), delegates.build(), optionalDeclarations.build(), ModuleKind.forAnnotatedElement(moduleElement).get()); } private void collectCompanionModuleBindings( XTypeElement companionModule, ImmutableSet.Builder bindings) { ImmutableSet bindingElementDescriptors = bindings.build().stream() .map( binding -> getMethodDescriptor(asExecutable(toJavac(binding.bindingElement().get())))) .collect(toImmutableSet()); methodsIn(elements.getAllMembers(toJavac(companionModule))).stream() .map(method -> toXProcessing(method, processingEnv)) .filter(XElementKt::isMethod) .map(XElements::asMethod) // 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 -> !method.hasAnnotation(TypeNames.JVM_STATIC)) // 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 (method.hasAnnotation(TypeNames.PROVIDES)) { bindings.add(bindingFactory.providesMethodBinding(method, companionModule)); } if (method.hasAnnotation(TypeNames.PRODUCES)) { bindings.add(bindingFactory.producesMethodBinding(method, companionModule)); } }); } /** Returns all the modules transitively included by given modules, including the arguments. */ ImmutableSet transitiveModules(Collection modules) { // Traverse as a graph to automatically handle modules with cyclic includes. return ImmutableSet.copyOf( Traverser.forGraph( (ModuleDescriptor module) -> transform(includedModules(module), this::create)) .depthFirstPreOrder(transform(modules, this::create))); } private ImmutableSet includedModules(ModuleDescriptor moduleDescriptor) { return ImmutableSet.copyOf( collectIncludedModules(new LinkedHashSet<>(), moduleDescriptor.moduleElement())); } private Set collectIncludedModules( Set includedModules, XTypeElement moduleElement) { XType superclass = moduleElement.getSuperType(); if (superclass != null) { verify(isDeclared(superclass)); if (!TypeName.OBJECT.equals(superclass.getTypeName())) { collectIncludedModules(includedModules, superclass.getTypeElement()); } } moduleAnnotation(moduleElement, superficialValidation) .ifPresent( moduleAnnotation -> { includedModules.addAll(moduleAnnotation.includes()); includedModules.addAll(implicitlyIncludedModules(moduleElement)); }); return includedModules; } private static final ClassName CONTRIBUTES_ANDROID_INJECTOR = ClassName.get("dagger.android", "ContributesAndroidInjector"); // @ContributesAndroidInjector generates a module that is implicitly included in the enclosing // module private ImmutableSet implicitlyIncludedModules(XTypeElement module) { if (processingEnv.findTypeElement(CONTRIBUTES_ANDROID_INJECTOR) == null) { return ImmutableSet.of(); } return module.getDeclaredMethods().stream() .filter(method -> method.hasAnnotation(CONTRIBUTES_ANDROID_INJECTOR)) .map( method -> DaggerSuperficialValidation.requireTypeElement( processingEnv, implicitlyIncludedModuleName(module, method))) .collect(toImmutableSet()); } private ClassName implicitlyIncludedModuleName(XTypeElement module, XMethodElement method) { return ClassName.get( module.getPackageName(), String.format( "%s_%s", classFileName(module.getClassName()), LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(method)))); } @Override public void clearCache() { cache.clear(); } } }