1 /* 2 * Copyright (C) 2021 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.hilt.processor.internal.aggregateddeps; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.base.Preconditions.checkState; 21 import static com.google.common.collect.Iterables.getOnlyElement; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 23 24 import com.google.auto.value.AutoValue; 25 import com.google.common.collect.ImmutableMap; 26 import com.google.common.collect.ImmutableSet; 27 import com.squareup.javapoet.ClassName; 28 import dagger.hilt.processor.internal.AggregatedElements; 29 import dagger.hilt.processor.internal.AnnotationValues; 30 import dagger.hilt.processor.internal.ClassNames; 31 import dagger.hilt.processor.internal.Processors; 32 import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr; 33 import java.util.Optional; 34 import java.util.stream.Collectors; 35 import javax.lang.model.element.AnnotationMirror; 36 import javax.lang.model.element.AnnotationValue; 37 import javax.lang.model.element.TypeElement; 38 import javax.lang.model.util.Elements; 39 40 /** 41 * A class that represents the values stored in an {@link 42 * dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps} annotation. 43 */ 44 @AutoValue 45 public abstract class AggregatedDepsMetadata { 46 private static final String AGGREGATED_DEPS_PACKAGE = "hilt_aggregated_deps"; 47 48 enum DependencyType { 49 MODULE, 50 ENTRY_POINT, 51 COMPONENT_ENTRY_POINT 52 } 53 54 /** Returns the aggregating element */ aggregatingElement()55 public abstract TypeElement aggregatingElement(); 56 testElement()57 public abstract Optional<TypeElement> testElement(); 58 componentElements()59 public abstract ImmutableSet<TypeElement> componentElements(); 60 dependencyType()61 abstract DependencyType dependencyType(); 62 dependency()63 abstract TypeElement dependency(); 64 replacedDependencies()65 public abstract ImmutableSet<TypeElement> replacedDependencies(); 66 isModule()67 public boolean isModule() { 68 return dependencyType() == DependencyType.MODULE; 69 } 70 71 /** Returns metadata for all aggregated elements in the aggregating package. */ from(Elements elements)72 public static ImmutableSet<AggregatedDepsMetadata> from(Elements elements) { 73 return from( 74 AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements), 75 elements); 76 } 77 78 /** Returns metadata for each aggregated element. */ from( ImmutableSet<TypeElement> aggregatedElements, Elements elements)79 public static ImmutableSet<AggregatedDepsMetadata> from( 80 ImmutableSet<TypeElement> aggregatedElements, Elements elements) { 81 return aggregatedElements.stream() 82 .map(aggregatedElement -> create(aggregatedElement, elements)) 83 .collect(toImmutableSet()); 84 } 85 toIr(AggregatedDepsMetadata metadata)86 public static AggregatedDepsIr toIr(AggregatedDepsMetadata metadata) { 87 return new AggregatedDepsIr( 88 ClassName.get(metadata.aggregatingElement()), 89 metadata.componentElements().stream() 90 .map(ClassName::get) 91 .collect(Collectors.toList()), 92 metadata.testElement() 93 .map(ClassName::get) 94 .orElse(null), 95 metadata.replacedDependencies().stream() 96 .map(ClassName::get) 97 .collect(Collectors.toList()), 98 metadata.dependencyType() == DependencyType.MODULE 99 ? ClassName.get(metadata.dependency()) 100 : null, 101 metadata.dependencyType() == DependencyType.ENTRY_POINT 102 ? ClassName.get(metadata.dependency()) 103 : null, 104 metadata.dependencyType() == DependencyType.COMPONENT_ENTRY_POINT 105 ? ClassName.get(metadata.dependency()) 106 : null); 107 } 108 create(TypeElement element, Elements elements)109 private static AggregatedDepsMetadata create(TypeElement element, Elements elements) { 110 AnnotationMirror annotationMirror = 111 Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS); 112 113 ImmutableMap<String, AnnotationValue> values = 114 Processors.getAnnotationValues(elements, annotationMirror); 115 116 return new AutoValue_AggregatedDepsMetadata( 117 element, 118 getTestElement(values.get("test"), elements), 119 getComponents(values.get("components"), elements), 120 getDependencyType( 121 values.get("modules"), values.get("entryPoints"), values.get("componentEntryPoints")), 122 getDependency( 123 values.get("modules"), 124 values.get("entryPoints"), 125 values.get("componentEntryPoints"), 126 elements), 127 getReplacedDependencies(values.get("replaces"), elements)); 128 } 129 getTestElement( AnnotationValue testValue, Elements elements)130 private static Optional<TypeElement> getTestElement( 131 AnnotationValue testValue, Elements elements) { 132 checkNotNull(testValue); 133 String test = AnnotationValues.getString(testValue); 134 return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test)); 135 } 136 getComponents( AnnotationValue componentsValue, Elements elements)137 private static ImmutableSet<TypeElement> getComponents( 138 AnnotationValue componentsValue, Elements elements) { 139 checkNotNull(componentsValue); 140 ImmutableSet<TypeElement> componentNames = 141 AnnotationValues.getAnnotationValues(componentsValue).stream() 142 .map(AnnotationValues::getString) 143 .map( 144 // This is a temporary hack to map the old ApplicationComponent to the new 145 // SingletonComponent. Technically, this is only needed for backwards compatibility 146 // with libraries using the old processor since new processors should convert to the 147 // new SingletonComponent when generating the metadata class. 148 componentName -> 149 componentName.contentEquals( 150 "dagger.hilt.android.components.ApplicationComponent") 151 ? ClassNames.SINGLETON_COMPONENT.canonicalName() 152 : componentName) 153 .map(elements::getTypeElement) 154 .collect(toImmutableSet()); 155 checkState(!componentNames.isEmpty()); 156 return componentNames; 157 } 158 getDependencyType( AnnotationValue modulesValue, AnnotationValue entryPointsValue, AnnotationValue componentEntryPointsValue)159 private static DependencyType getDependencyType( 160 AnnotationValue modulesValue, 161 AnnotationValue entryPointsValue, 162 AnnotationValue componentEntryPointsValue) { 163 checkNotNull(modulesValue); 164 checkNotNull(entryPointsValue); 165 checkNotNull(componentEntryPointsValue); 166 167 ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder(); 168 if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) { 169 dependencyTypes.add(DependencyType.MODULE); 170 } 171 if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) { 172 dependencyTypes.add(DependencyType.ENTRY_POINT); 173 } 174 if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) { 175 dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT); 176 } 177 return getOnlyElement(dependencyTypes.build()); 178 } 179 getDependency( AnnotationValue modulesValue, AnnotationValue entryPointsValue, AnnotationValue componentEntryPointsValue, Elements elements)180 private static TypeElement getDependency( 181 AnnotationValue modulesValue, 182 AnnotationValue entryPointsValue, 183 AnnotationValue componentEntryPointsValue, 184 Elements elements) { 185 checkNotNull(modulesValue); 186 checkNotNull(entryPointsValue); 187 checkNotNull(componentEntryPointsValue); 188 189 return elements.getTypeElement( 190 AnnotationValues.getString( 191 getOnlyElement( 192 ImmutableSet.<AnnotationValue>builder() 193 .addAll(AnnotationValues.getAnnotationValues(modulesValue)) 194 .addAll(AnnotationValues.getAnnotationValues(entryPointsValue)) 195 .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue)) 196 .build()))); 197 } 198 getReplacedDependencies( AnnotationValue replacedDependenciesValue, Elements elements)199 private static ImmutableSet<TypeElement> getReplacedDependencies( 200 AnnotationValue replacedDependenciesValue, Elements elements) { 201 // Allow null values to support libraries using a Hilt version before @TestInstallIn was added 202 return replacedDependenciesValue == null 203 ? ImmutableSet.of() 204 : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream() 205 .map(AnnotationValues::getString) 206 .map(elements::getTypeElement) 207 .map(replacedDep -> getPublicDependency(replacedDep, elements)) 208 .collect(toImmutableSet()); 209 } 210 211 /** Returns the public Hilt wrapper module, or the module itself if its already public. */ getPublicDependency(TypeElement dependency, Elements elements)212 private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) { 213 return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE) 214 .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString())) 215 .orElse(dependency); 216 } 217 } 218