/* * 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 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 childPath) { if (!metadatas.containsKey(element)) { metadatas.put(element, getUncached(element, childPath)); } return metadatas.get(element); } private DefineComponentMetadata getUncached( Element element, LinkedHashSet 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 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 scopes = Processors.getScopeAnnotations(component).stream() .map(AnnotationMirror::getAnnotationType) .map(MoreTypes::asTypeElement) .collect(toImmutableList()); ImmutableList 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. !"".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 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 scopes(); /** Returns the parent component, if one exists. */ abstract Optional parentMetadata(); boolean isRoot() { return !parentMetadata().isPresent(); } } }