blob: f13aa50c0422caa1118204a7cfd16d3f3607c85f [file] [log] [blame]
/*
* 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.binding;
import static com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
import static dagger.internal.codegen.base.Scopes.productionScope;
import static dagger.internal.codegen.base.Scopes.scopesOf;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.creatorAnnotationsFor;
import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentContributionMethod;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.isSubcomponentCreator;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static javax.lang.model.type.TypeKind.DECLARED;
import static javax.lang.model.type.TypeKind.VOID;
import static javax.lang.model.util.ElementFilter.methodsIn;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import dagger.internal.codegen.base.ComponentAnnotation;
import dagger.internal.codegen.base.ModuleAnnotation;
import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.Scope;
import java.util.Optional;
import java.util.function.Function;
import javax.inject.Inject;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
/** A factory for {@link ComponentDescriptor}s. */
public final class ComponentDescriptorFactory {
private final DaggerElements elements;
private final DaggerTypes types;
private final DependencyRequestFactory dependencyRequestFactory;
private final ModuleDescriptor.Factory moduleDescriptorFactory;
private final InjectionAnnotations injectionAnnotations;
@Inject
ComponentDescriptorFactory(
DaggerElements elements,
DaggerTypes types,
DependencyRequestFactory dependencyRequestFactory,
ModuleDescriptor.Factory moduleDescriptorFactory,
InjectionAnnotations injectionAnnotations) {
this.elements = elements;
this.types = types;
this.dependencyRequestFactory = dependencyRequestFactory;
this.moduleDescriptorFactory = moduleDescriptorFactory;
this.injectionAnnotations = injectionAnnotations;
}
/** Returns a descriptor for a root component type. */
public ComponentDescriptor rootComponentDescriptor(TypeElement typeElement) {
return create(
typeElement,
checkAnnotation(
typeElement,
ComponentAnnotation::rootComponentAnnotation,
"must have a component annotation"));
}
/** Returns a descriptor for a subcomponent type. */
public ComponentDescriptor subcomponentDescriptor(TypeElement typeElement) {
return create(
typeElement,
checkAnnotation(
typeElement,
ComponentAnnotation::subcomponentAnnotation,
"must have a subcomponent annotation"));
}
/**
* Returns a descriptor for a fictional component based on a module type in order to validate its
* bindings.
*/
public ComponentDescriptor moduleComponentDescriptor(TypeElement typeElement) {
return create(
typeElement,
ComponentAnnotation.fromModuleAnnotation(
checkAnnotation(
typeElement, ModuleAnnotation::moduleAnnotation, "must have a module annotation")));
}
private static <A> A checkAnnotation(
TypeElement typeElement,
Function<TypeElement, Optional<A>> annotationFunction,
String message) {
return annotationFunction
.apply(typeElement)
.orElseThrow(() -> new IllegalArgumentException(typeElement + " " + message));
}
private ComponentDescriptor create(
TypeElement typeElement, ComponentAnnotation componentAnnotation) {
ImmutableSet<ComponentRequirement> componentDependencies =
componentAnnotation.dependencyTypes().stream()
.map(ComponentRequirement::forDependency)
.collect(toImmutableSet());
ImmutableMap.Builder<ExecutableElement, ComponentRequirement> dependenciesByDependencyMethod =
ImmutableMap.builder();
for (ComponentRequirement componentDependency : componentDependencies) {
for (ExecutableElement dependencyMethod :
methodsIn(elements.getAllMembers(componentDependency.typeElement()))) {
if (isComponentContributionMethod(elements, dependencyMethod)) {
dependenciesByDependencyMethod.put(dependencyMethod, componentDependency);
}
}
}
// Start with the component's modules. For fictional components built from a module, start with
// that module.
ImmutableSet<TypeElement> modules =
componentAnnotation.isRealComponent()
? componentAnnotation.modules()
: ImmutableSet.of(typeElement);
ImmutableSet<ModuleDescriptor> transitiveModules =
moduleDescriptorFactory.transitiveModules(modules);
ImmutableSet.Builder<ComponentDescriptor> subcomponentsFromModules = ImmutableSet.builder();
for (ModuleDescriptor module : transitiveModules) {
for (SubcomponentDeclaration subcomponentDeclaration : module.subcomponentDeclarations()) {
TypeElement subcomponent = subcomponentDeclaration.subcomponentType();
subcomponentsFromModules.add(subcomponentDescriptor(subcomponent));
}
}
ImmutableSet.Builder<ComponentMethodDescriptor> componentMethodsBuilder =
ImmutableSet.builder();
ImmutableBiMap.Builder<ComponentMethodDescriptor, ComponentDescriptor>
subcomponentsByFactoryMethod = ImmutableBiMap.builder();
ImmutableBiMap.Builder<ComponentMethodDescriptor, ComponentDescriptor>
subcomponentsByBuilderMethod = ImmutableBiMap.builder();
if (componentAnnotation.isRealComponent()) {
ImmutableSet<ExecutableElement> unimplementedMethods =
elements.getUnimplementedMethods(typeElement);
for (ExecutableElement componentMethod : unimplementedMethods) {
ComponentMethodDescriptor componentMethodDescriptor =
getDescriptorForComponentMethod(typeElement, componentAnnotation, componentMethod);
componentMethodsBuilder.add(componentMethodDescriptor);
componentMethodDescriptor
.subcomponent()
.ifPresent(
subcomponent -> {
// If the dependency request is present, that means the method returns the
// subcomponent factory.
if (componentMethodDescriptor.dependencyRequest().isPresent()) {
subcomponentsByBuilderMethod.put(componentMethodDescriptor, subcomponent);
} else {
subcomponentsByFactoryMethod.put(componentMethodDescriptor, subcomponent);
}
});
}
}
// Validation should have ensured that this set will have at most one element.
ImmutableSet<DeclaredType> enclosedCreators =
creatorAnnotationsFor(componentAnnotation).stream()
.flatMap(
creatorAnnotation ->
enclosedAnnotatedTypes(typeElement, creatorAnnotation).stream())
.collect(toImmutableSet());
Optional<ComponentCreatorDescriptor> creatorDescriptor =
enclosedCreators.isEmpty()
? Optional.empty()
: Optional.of(
ComponentCreatorDescriptor.create(
getOnlyElement(enclosedCreators), elements, types, dependencyRequestFactory));
ImmutableSet<Scope> scopes = scopesOf(typeElement);
if (componentAnnotation.isProduction()) {
scopes = ImmutableSet.<Scope>builder().addAll(scopes).add(productionScope(elements)).build();
}
return new AutoValue_ComponentDescriptor(
componentAnnotation,
typeElement,
componentDependencies,
transitiveModules,
dependenciesByDependencyMethod.build(),
scopes,
subcomponentsFromModules.build(),
subcomponentsByFactoryMethod.build(),
subcomponentsByBuilderMethod.build(),
componentMethodsBuilder.build(),
creatorDescriptor);
}
private ComponentMethodDescriptor getDescriptorForComponentMethod(
TypeElement componentElement,
ComponentAnnotation componentAnnotation,
ExecutableElement componentMethod) {
ComponentMethodDescriptor.Builder descriptor =
ComponentMethodDescriptor.builder(componentMethod);
ExecutableType resolvedComponentMethod =
MoreTypes.asExecutable(
types.asMemberOf(MoreTypes.asDeclared(componentElement.asType()), componentMethod));
TypeMirror returnType = resolvedComponentMethod.getReturnType();
if (returnType.getKind().equals(DECLARED)
&& !injectionAnnotations.getQualifier(componentMethod).isPresent()) {
TypeElement returnTypeElement = asTypeElement(returnType);
if (subcomponentAnnotation(returnTypeElement).isPresent()) {
// It's a subcomponent factory method. There is no dependency request, and there could be
// any number of parameters. Just return the descriptor.
return descriptor.subcomponent(subcomponentDescriptor(returnTypeElement)).build();
}
if (isSubcomponentCreator(returnTypeElement)) {
descriptor.subcomponent(
subcomponentDescriptor(asType(returnTypeElement.getEnclosingElement())));
}
}
switch (componentMethod.getParameters().size()) {
case 0:
checkArgument(
!returnType.getKind().equals(VOID),
"component method cannot be void: %s",
componentMethod);
descriptor.dependencyRequest(
componentAnnotation.isProduction()
? dependencyRequestFactory.forComponentProductionMethod(
componentMethod, resolvedComponentMethod)
: dependencyRequestFactory.forComponentProvisionMethod(
componentMethod, resolvedComponentMethod));
break;
case 1:
checkArgument(
returnType.getKind().equals(VOID)
|| MoreTypes.equivalence()
.equivalent(returnType, resolvedComponentMethod.getParameterTypes().get(0)),
"members injection method must return void or parameter type: %s",
componentMethod);
descriptor.dependencyRequest(
dependencyRequestFactory.forComponentMembersInjectionMethod(
componentMethod, resolvedComponentMethod));
break;
default:
throw new IllegalArgumentException(
"component method has too many parameters: " + componentMethod);
}
return descriptor.build();
}
}