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.root; 18 19 import static javax.lang.model.element.Modifier.PUBLIC; 20 21 import androidx.room.compiler.processing.XFiler.Mode; 22 import androidx.room.compiler.processing.XProcessingEnv; 23 import androidx.room.compiler.processing.XTypeElement; 24 import com.google.common.collect.ImmutableSet; 25 import com.squareup.javapoet.AnnotationSpec; 26 import com.squareup.javapoet.ClassName; 27 import com.squareup.javapoet.JavaFile; 28 import com.squareup.javapoet.TypeSpec; 29 import dagger.hilt.processor.internal.AggregatedElements; 30 import dagger.hilt.processor.internal.ClassNames; 31 import dagger.hilt.processor.internal.Processors; 32 import java.io.IOException; 33 import java.util.HashSet; 34 import java.util.Optional; 35 import java.util.Set; 36 37 /** Generates an {@link dagger.hilt.internal.componenttreedeps.ComponentTreeDeps}. */ 38 final class ComponentTreeDepsGenerator { 39 // Keeps track of already generated proxies. For correctness, this same instance of 40 // ComponentTreeDepsGenerator must be used for a given round. 41 private final Set<ClassName> generatedProxies = new HashSet<>(); 42 private final XProcessingEnv env; 43 private final Mode mode; 44 ComponentTreeDepsGenerator(XProcessingEnv env, Mode mode)45 ComponentTreeDepsGenerator(XProcessingEnv env, Mode mode) { 46 this.env = env; 47 this.mode = mode; 48 } 49 generate(ComponentTreeDepsMetadata metadata)50 void generate(ComponentTreeDepsMetadata metadata) throws IOException { 51 ClassName name = metadata.name(); 52 TypeSpec.Builder builder = 53 TypeSpec.classBuilder(name) 54 // No originating element since this is generated by the aggregating processor. 55 .addAnnotation(componentTreeDepsAnnotation(metadata)); 56 57 Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString()); 58 59 env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode); 60 } 61 componentTreeDepsAnnotation(ComponentTreeDepsMetadata metadata)62 AnnotationSpec componentTreeDepsAnnotation(ComponentTreeDepsMetadata metadata) 63 throws IOException { 64 AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassNames.COMPONENT_TREE_DEPS); 65 addDeps(builder, metadata.aggregatedRootDeps(), "rootDeps"); 66 addDeps(builder, metadata.defineComponentDeps(), "defineComponentDeps"); 67 addDeps(builder, metadata.aliasOfDeps(), "aliasOfDeps"); 68 addDeps(builder, metadata.aggregatedDeps(), "aggregatedDeps"); 69 addDeps(builder, metadata.aggregatedUninstallModulesDeps(), "uninstallModulesDeps"); 70 addDeps(builder, metadata.aggregatedEarlyEntryPointDeps(), "earlyEntryPointDeps"); 71 return builder.build(); 72 } 73 addDeps(AnnotationSpec.Builder builder, ImmutableSet<XTypeElement> deps, String name)74 private void addDeps(AnnotationSpec.Builder builder, ImmutableSet<XTypeElement> deps, String name) 75 throws IOException { 76 for (XTypeElement dep : deps) { 77 builder.addMember(name, "$T.class", maybeWrapInPublicProxy(dep)); 78 } 79 } 80 81 /** 82 * This method will return the public proxy for {@code dep} if it is not public, otherwise it will 83 * return {@code dep} itself. It will also generate the proxy if it doesn't already exist. 84 * 85 * <p>Note: These proxies are only used for serialization. The proxy will be unwrapped when 86 * converting to {@link ComponentTreeDepsMetadata}. 87 * 88 * <p>Note: The public proxy is needed because Hilt versions < 2.35 generated package-private 89 * aggregating elements, which can't be referenced directly in the {@code @ComponentTreeDeps}. 90 */ maybeWrapInPublicProxy(XTypeElement dep)91 private ClassName maybeWrapInPublicProxy(XTypeElement dep) { 92 Optional<ClassName> proxyName = AggregatedElements.aggregatedElementProxyName(dep); 93 if (proxyName.isPresent()) { 94 // Check the set of already generated proxies to ensure we don't regenerate the proxy in 95 // this round. Also check that the element doesn't already exist to ensure we don't regenerate 96 // a proxy generated in a previous round. 97 if (generatedProxies.add(proxyName.get()) 98 && env.findTypeElement(proxyName.get().canonicalName()) == null) { 99 generateProxy(dep, proxyName.get()); 100 } 101 return proxyName.get(); 102 } 103 return dep.getClassName(); 104 } 105 generateProxy(XTypeElement dep, ClassName proxyName)106 private void generateProxy(XTypeElement dep, ClassName proxyName) { 107 TypeSpec.Builder builder = 108 TypeSpec.classBuilder(proxyName) 109 .addModifiers(PUBLIC) 110 // No originating element since this is generated by the aggregating processor. 111 .addAnnotation( 112 AnnotationSpec.builder(ClassNames.AGGREGATED_ELEMENT_PROXY) 113 .addMember("value", "$T.class", dep.getClassName()) 114 .build()); 115 116 Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString()); 117 env.getFiler().write(JavaFile.builder(proxyName.packageName(), builder.build()).build(), mode); 118 } 119 } 120