blob: aa69e40e119a512d2905c65aa8873a87ca8cf467 [file] [log] [blame]
/*
* Copyright (C) 2019 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.hilt.processor.internal.definecomponent;
import static com.google.auto.common.AnnotationMirrors.getAnnotationElementAndValue;
import static com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static java.util.stream.Collectors.joining;
import static javax.lang.model.element.Modifier.STATIC;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.squareup.javapoet.ClassName;
import dagger.hilt.processor.internal.AnnotationValues;
import dagger.hilt.processor.internal.ClassNames;
import dagger.hilt.processor.internal.ProcessorErrors;
import dagger.hilt.processor.internal.Processors;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
/** Metadata for types annotated with {@link dagger.hilt.DefineComponent}. */
final class DefineComponentMetadatas {
static DefineComponentMetadatas create() {
return new DefineComponentMetadatas();
}
private final Map<Element, DefineComponentMetadata> metadatas = new HashMap<>();
private DefineComponentMetadatas() {}
/** Returns the metadata for an element annotated with {@link dagger.hilt.DefineComponent}. */
DefineComponentMetadata get(Element element) {
return get(element, new LinkedHashSet<>());
}
private DefineComponentMetadata get(Element element, LinkedHashSet<Element> childPath) {
if (!metadatas.containsKey(element)) {
metadatas.put(element, getUncached(element, childPath));
}
return metadatas.get(element);
}
private DefineComponentMetadata getUncached(
Element element, LinkedHashSet<Element> childPath) {
ProcessorErrors.checkState(
childPath.add(element),
element,
"@DefineComponent cycle: %s -> %s",
childPath.stream().map(Object::toString).collect(joining(" -> ")),
element);
ProcessorErrors.checkState(
Processors.hasAnnotation(element, ClassNames.DEFINE_COMPONENT),
element,
"%s, expected to be annotated with @DefineComponent. Found: %s",
element,
element.getAnnotationMirrors());
// TODO(bcorso): Allow abstract classes?
ProcessorErrors.checkState(
element.getKind().equals(ElementKind.INTERFACE),
element,
"@DefineComponent is only allowed on interfaces. Found: %s",
element);
TypeElement component = asType(element);
// TODO(bcorso): Allow extending interfaces?
ProcessorErrors.checkState(
component.getInterfaces().isEmpty(),
component,
"@DefineComponent %s, cannot extend a super class or interface. Found: %s",
component,
component.getInterfaces());
// TODO(bcorso): Allow type parameters?
ProcessorErrors.checkState(
component.getTypeParameters().isEmpty(),
component,
"@DefineComponent %s, cannot have type parameters.",
component.asType());
// TODO(bcorso): Allow non-static abstract methods (aka EntryPoints)?
List<ExecutableElement> nonStaticMethods =
ElementFilter.methodsIn(component.getEnclosedElements()).stream()
.filter(method -> !method.getModifiers().contains(STATIC))
.collect(Collectors.toList());
ProcessorErrors.checkState(
nonStaticMethods.isEmpty(),
component,
"@DefineComponent %s, cannot have non-static methods. Found: %s",
component,
nonStaticMethods);
// No need to check non-static fields since interfaces can't have them.
ImmutableList<TypeElement> scopes =
Processors.getScopeAnnotations(component).stream()
.map(AnnotationMirror::getAnnotationType)
.map(MoreTypes::asTypeElement)
.collect(toImmutableList());
ImmutableList<AnnotationMirror> aliasScopes =
Processors.getAnnotationsAnnotatedWith(component, ClassNames.ALIAS_OF);
ProcessorErrors.checkState(
aliasScopes.isEmpty(),
component,
"@DefineComponent %s, references invalid scope(s) annotated with @AliasOf. "
+ "@DefineComponent scopes cannot be aliases of other scopes: %s",
component,
aliasScopes);
AnnotationMirror mirror =
Processors.getAnnotationMirror(component, ClassNames.DEFINE_COMPONENT);
AnnotationValue parentValue = getAnnotationElementAndValue(mirror, "parent").getValue();
ProcessorErrors.checkState(
// TODO(bcorso): Contribute a check to auto/common AnnotationValues.
!"<error>".contentEquals(parentValue.getValue().toString()),
component,
"@DefineComponent %s, references an invalid parent type: %s",
component,
mirror);
TypeElement parent = asTypeElement(AnnotationValues.getTypeMirror(parentValue));
ProcessorErrors.checkState(
ClassName.get(parent).equals(ClassNames.DEFINE_COMPONENT_NO_PARENT)
|| Processors.hasAnnotation(parent, ClassNames.DEFINE_COMPONENT),
component,
"@DefineComponent %s, references a type not annotated with @DefineComponent: %s",
component,
parent);
Optional<DefineComponentMetadata> parentComponent =
ClassName.get(parent).equals(ClassNames.DEFINE_COMPONENT_NO_PARENT)
? Optional.empty()
: Optional.of(get(parent, childPath));
ProcessorErrors.checkState(
parentComponent.isPresent()
|| ClassName.get(component).equals(ClassNames.SINGLETON_COMPONENT),
component,
"@DefineComponent %s is missing a parent declaration.\n"
+ "Please declare the parent, for example: @DefineComponent(parent ="
+ " SingletonComponent.class)",
component);
return new AutoValue_DefineComponentMetadatas_DefineComponentMetadata(
component, scopes, parentComponent);
}
@AutoValue
abstract static class DefineComponentMetadata {
/** Returns the component annotated with {@link dagger.hilt.DefineComponent}. */
abstract TypeElement component();
/** Returns the scopes of the component. */
abstract ImmutableList<TypeElement> scopes();
/** Returns the parent component, if one exists. */
abstract Optional<DefineComponentMetadata> parentMetadata();
boolean isRoot() {
return !parentMetadata().isPresent();
}
}
}