/* * 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 dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.common.MoreElements; import com.google.auto.value.AutoValue; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; import dagger.hilt.processor.internal.ComponentTree; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata; import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; /** * A utility class for getting {@link DefineComponentMetadata} and {@link * DefineComponentBuilderMetadata}. */ public final class DefineComponents { static final String AGGREGATING_PACKAGE = DefineComponents.class.getPackage().getName() + ".codegen"; public static DefineComponents create() { return new DefineComponents(); } private final Map componentDescriptors = new HashMap<>(); private final DefineComponentMetadatas componentMetadatas = DefineComponentMetadatas.create(); private final DefineComponentBuilderMetadatas componentBuilderMetadatas = DefineComponentBuilderMetadatas.create(componentMetadatas); private DefineComponents() {} /** Returns the {@link ComponentDescriptor} for the given component element. */ // TODO(b/144940889): This descriptor doesn't contain the "creator" or the "installInName". public ComponentDescriptor componentDescriptor(Element element) { if (!componentDescriptors.containsKey(element)) { componentDescriptors.put(element, uncachedComponentDescriptor(element)); } return componentDescriptors.get(element); } private ComponentDescriptor uncachedComponentDescriptor(Element element) { DefineComponentMetadata metadata = componentMetadatas.get(element); ComponentDescriptor.Builder builder = ComponentDescriptor.builder() .component(ClassName.get(metadata.component())) .scopes(metadata.scopes().stream().map(ClassName::get).collect(toImmutableSet())); metadata.parentMetadata() .map(DefineComponentMetadata::component) .map(this::componentDescriptor) .ifPresent(builder::parent); return builder.build(); } /** Returns the {@link ComponentTree} from the aggregated {@link ComponentDescriptor}s. */ public ComponentTree getComponentTree(Elements elements) { AggregatedMetadata aggregatedMetadata = AggregatedMetadata.from(elements, componentMetadatas, componentBuilderMetadatas); ListMultimap builderMultimap = ArrayListMultimap.create(); aggregatedMetadata.builders() .forEach(builder -> builderMultimap.put(builder.componentMetadata(), builder)); // Check that there are not multiple builders per component for (DefineComponentMetadata componentMetadata : builderMultimap.keySet()) { TypeElement component = componentMetadata.component(); ProcessorErrors.checkState( builderMultimap.get(componentMetadata).size() <= 1, component, "Multiple @%s declarations are not allowed for @%s type, %s. Found: %s", ClassNames.DEFINE_COMPONENT_BUILDER, ClassNames.DEFINE_COMPONENT, component, builderMultimap.get(componentMetadata).stream() .map(DefineComponentBuilderMetadata::builder) .map(TypeElement::toString) .sorted() .collect(toImmutableList())); } // Now that we know there is at most 1 builder per component, convert the Multimap to Map. Map builderMap = new LinkedHashMap<>(); builderMultimap.entries().forEach(e -> builderMap.put(e.getKey(), e.getValue())); return ComponentTree.from(aggregatedMetadata.components().stream() .map(componentMetadata -> toComponentDescriptor(componentMetadata, builderMap)) .collect(toImmutableSet())); } private static ComponentDescriptor toComponentDescriptor( DefineComponentMetadata componentMetadata, Map builderMap) { ComponentDescriptor.Builder builder = ComponentDescriptor.builder() .component(ClassName.get(componentMetadata.component())) .scopes( componentMetadata.scopes().stream().map(ClassName::get).collect(toImmutableSet())); if (builderMap.containsKey(componentMetadata)) { builder.creator(ClassName.get(builderMap.get(componentMetadata).builder())); } componentMetadata .parentMetadata() .map(parent -> toComponentDescriptor(parent, builderMap)) .ifPresent(builder::parent); return builder.build(); } @AutoValue abstract static class AggregatedMetadata { /** Returns the aggregated metadata for {@link DefineComponentClasses#component()}. */ abstract ImmutableList components(); /** Returns the aggregated metadata for {@link DefineComponentClasses#builder()}. */ abstract ImmutableList builders(); static AggregatedMetadata from( Elements elements, DefineComponentMetadatas componentMetadatas, DefineComponentBuilderMetadatas componentBuilderMetadatas) { PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE); if (packageElement == null) { return new AutoValue_DefineComponents_AggregatedMetadata( ImmutableList.of(), ImmutableList.of()); } ImmutableList.Builder components = ImmutableList.builder(); ImmutableList.Builder builders = ImmutableList.builder(); for (Element element : packageElement.getEnclosedElements()) { ProcessorErrors.checkState( MoreElements.isType(element), element, "Only types may be in package %s. Did you add custom code in the package?", packageElement); TypeElement typeElement = MoreElements.asType(element); ProcessorErrors.checkState( Processors.hasAnnotation(typeElement, ClassNames.DEFINE_COMPONENT_CLASSES), typeElement, "Class, %s, must be annotated with @%s. Found: %s.", typeElement, ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(), typeElement.getAnnotationMirrors()); Optional component = defineComponentClass(elements, typeElement, "component"); Optional builder = defineComponentClass(elements, typeElement, "builder"); ProcessorErrors.checkState( component.isPresent() || builder.isPresent(), typeElement, "@DefineComponentClasses missing both `component` and `builder` members."); component.map(componentMetadatas::get).ifPresent(components::add); builder.map(componentBuilderMetadatas::get).ifPresent(builders::add); } return new AutoValue_DefineComponents_AggregatedMetadata( components.build(), builders.build()); } private static Optional defineComponentClass( Elements elements, Element element, String annotationMember) { AnnotationMirror mirror = Processors.getAnnotationMirror(element, ClassNames.DEFINE_COMPONENT_CLASSES); AnnotationValue value = getAnnotationElementAndValue(mirror, annotationMember).getValue(); String className = AnnotationValues.getString(value); if (className.isEmpty()) { // The default value. return Optional.empty(); } TypeElement type = elements.getTypeElement(className); ProcessorErrors.checkState( type != null, element, "%s.%s(), has invalid value: `%s`.", ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(), annotationMember, className); return Optional.of(type); } } }