1 /* 2 * Copyright (C) 2019 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.android.processor.internal.androidentrypoint; 18 19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 20 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 21 import static kotlin.streams.jdk8.StreamsKt.asStream; 22 23 import androidx.room.compiler.processing.JavaPoetExtKt; 24 import androidx.room.compiler.processing.XFiler; 25 import androidx.room.compiler.processing.XMethodElement; 26 import androidx.room.compiler.processing.XProcessingEnv; 27 import androidx.room.compiler.processing.XTypeParameterElement; 28 import com.google.common.collect.ImmutableSet; 29 import com.squareup.javapoet.ClassName; 30 import com.squareup.javapoet.CodeBlock; 31 import com.squareup.javapoet.FieldSpec; 32 import com.squareup.javapoet.JavaFile; 33 import com.squareup.javapoet.MethodSpec; 34 import com.squareup.javapoet.ParameterSpec; 35 import com.squareup.javapoet.TypeName; 36 import com.squareup.javapoet.TypeSpec; 37 import dagger.hilt.android.processor.internal.AndroidClassNames; 38 import dagger.hilt.processor.internal.ComponentNames; 39 import dagger.hilt.processor.internal.ProcessorErrors; 40 import dagger.hilt.processor.internal.Processors; 41 import dagger.internal.codegen.xprocessing.XElements; 42 import java.io.IOException; 43 import java.util.stream.Stream; 44 import javax.lang.model.element.Modifier; 45 46 /** Generates an Hilt Application for an @AndroidEntryPoint app class. */ 47 public final class ApplicationGenerator { 48 private final XProcessingEnv env; 49 private final AndroidEntryPointMetadata metadata; 50 private final ClassName wrapperClassName; 51 private final ComponentNames componentNames; 52 ApplicationGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)53 public ApplicationGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) { 54 this.env = env; 55 this.metadata = metadata; 56 this.wrapperClassName = metadata.generatedClassName(); 57 this.componentNames = ComponentNames.withoutRenaming(); 58 } 59 60 // @Generated("ApplicationGenerator") 61 // abstract class Hilt_$APP extends $BASE implements ComponentManager<ApplicationComponent> { 62 // ... 63 // } generate()64 public void generate() throws IOException { 65 TypeSpec.Builder typeSpecBuilder = 66 TypeSpec.classBuilder(wrapperClassName.simpleName()) 67 .superclass(metadata.baseClassName()) 68 .addModifiers(metadata.generatedClassModifiers()) 69 .addField(injectedField()); 70 71 JavaPoetExtKt.addOriginatingElement(typeSpecBuilder, metadata.element()); 72 73 typeSpecBuilder 74 .addField(componentManagerField()) 75 .addMethod(componentManagerMethod()); 76 77 Generators.addGeneratedBaseClassJavadoc(typeSpecBuilder, AndroidClassNames.HILT_ANDROID_APP); 78 Processors.addGeneratedAnnotation(typeSpecBuilder, env, getClass()); 79 80 metadata.baseElement().getTypeParameters().stream() 81 .map(XTypeParameterElement::getTypeVariableName) 82 .forEachOrdered(typeSpecBuilder::addTypeVariable); 83 84 Generators.copyLintAnnotations(metadata.element(), typeSpecBuilder); 85 Generators.copySuppressAnnotations(metadata.element(), typeSpecBuilder); 86 Generators.addComponentOverride(metadata, typeSpecBuilder); 87 88 if (hasCustomInject()) { 89 typeSpecBuilder.addSuperinterface(AndroidClassNames.HAS_CUSTOM_INJECT); 90 typeSpecBuilder.addMethod(customInjectMethod()).addMethod(injectionMethod()); 91 } else { 92 typeSpecBuilder.addMethod(onCreateMethod()).addMethod(injectionMethod()); 93 } 94 95 env.getFiler() 96 .write( 97 JavaFile.builder(metadata.elementClassName().packageName(), typeSpecBuilder.build()) 98 .build(), 99 XFiler.Mode.Isolating); 100 } 101 hasCustomInject()102 private boolean hasCustomInject() { 103 boolean hasCustomInject = metadata.element().hasAnnotation(AndroidClassNames.CUSTOM_INJECT); 104 if (hasCustomInject) { 105 // Check that the Hilt base class does not already define a customInject implementation. 106 ImmutableSet<XMethodElement> customInjectMethods = 107 Stream.concat( 108 metadata.element().getDeclaredMethods().stream(), 109 asStream(metadata.baseElement().getAllMethods())) 110 .filter(method -> getSimpleName(method).contentEquals("customInject")) 111 .filter(method -> method.getParameters().isEmpty()) 112 .collect(toImmutableSet()); 113 114 for (XMethodElement customInjectMethod : customInjectMethods) { 115 ProcessorErrors.checkState( 116 customInjectMethod.isAbstract() && customInjectMethod.isProtected(), 117 customInjectMethod, 118 "%s#%s, must have modifiers `abstract` and `protected` when using @CustomInject.", 119 XElements.toStableString(customInjectMethod.getEnclosingElement()), 120 XElements.toStableString(customInjectMethod)); 121 } 122 } 123 return hasCustomInject; 124 } 125 126 // private final ApplicationComponentManager<ApplicationComponent> componentManager = 127 // new ApplicationComponentManager(/* creatorType */); componentManagerField()128 private FieldSpec componentManagerField() { 129 ParameterSpec managerParam = metadata.componentManagerParam(); 130 return FieldSpec.builder(managerParam.type, managerParam.name) 131 .addModifiers(Modifier.PRIVATE, Modifier.FINAL) 132 .initializer("new $T($L)", AndroidClassNames.APPLICATION_COMPONENT_MANAGER, creatorType()) 133 .build(); 134 } 135 136 // protected ApplicationComponentManager<ApplicationComponent> componentManager() { 137 // return componentManager(); 138 // } componentManagerMethod()139 private MethodSpec componentManagerMethod() { 140 return MethodSpec.methodBuilder("componentManager") 141 .addAnnotation(Override.class) 142 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 143 .returns(metadata.componentManagerParam().type) 144 .addStatement("return $N", metadata.componentManagerParam()) 145 .build(); 146 } 147 148 // new Supplier<ApplicationComponent>() { 149 // @Override 150 // public ApplicationComponent get() { 151 // return DaggerApplicationComponent.builder() 152 // .applicationContextModule(new ApplicationContextModule(Hilt_$APP.this)) 153 // .build(); 154 // } 155 // } creatorType()156 private TypeSpec creatorType() { 157 return TypeSpec.anonymousClassBuilder("") 158 .addSuperinterface(AndroidClassNames.COMPONENT_SUPPLIER) 159 .addMethod( 160 MethodSpec.methodBuilder("get") 161 .addAnnotation(Override.class) 162 .addModifiers(Modifier.PUBLIC) 163 .returns(TypeName.OBJECT) 164 .addCode(componentBuilder()) 165 .build()) 166 .build(); 167 } 168 169 // return DaggerApplicationComponent.builder() 170 // .applicationContextModule(new ApplicationContextModule(Hilt_$APP.this)) 171 // .build(); componentBuilder()172 private CodeBlock componentBuilder() { 173 ClassName component = 174 componentNames.generatedComponent( 175 metadata.elementClassName(), AndroidClassNames.SINGLETON_COMPONENT); 176 return CodeBlock.builder() 177 .addStatement( 178 "return $T.builder()$Z" + ".applicationContextModule(new $T($T.this))$Z" + ".build()", 179 Processors.prepend(Processors.getEnclosedClassName(component), "Dagger"), 180 AndroidClassNames.APPLICATION_CONTEXT_MODULE, 181 wrapperClassName) 182 .build(); 183 } 184 185 // @CallSuper 186 // @Override 187 // public void onCreate() { 188 // hiltInternalInject(); 189 // super.onCreate(); 190 // } onCreateMethod()191 private MethodSpec onCreateMethod() { 192 return MethodSpec.methodBuilder("onCreate") 193 .addAnnotation(AndroidClassNames.CALL_SUPER) 194 .addAnnotation(Override.class) 195 .addModifiers(Modifier.PUBLIC) 196 .addStatement("hiltInternalInject()") 197 .addStatement("super.onCreate()") 198 .build(); 199 } 200 201 // public void hiltInternalInject() { 202 // if (!injected) { 203 // injected = true; 204 // // This is a known unsafe cast but should be fine if the only use is 205 // // $APP extends Hilt_$APP 206 // generatedComponent().inject(($APP) this); 207 // } 208 // } injectionMethod()209 private MethodSpec injectionMethod() { 210 return MethodSpec.methodBuilder("hiltInternalInject") 211 .addModifiers(Modifier.PROTECTED) 212 .beginControlFlow("if (!injected)") 213 .addStatement("injected = true") 214 .addCode(injectCodeBlock()) 215 .endControlFlow() 216 .build(); 217 } 218 219 // private boolean injected = false; injectedField()220 private static FieldSpec injectedField() { 221 return FieldSpec.builder(TypeName.BOOLEAN, "injected") 222 .addModifiers(Modifier.PRIVATE) 223 .initializer("false") 224 .build(); 225 } 226 227 // @Override 228 // public final void customInject() { 229 // hiltInternalInject(); 230 // } customInjectMethod()231 private MethodSpec customInjectMethod() { 232 return MethodSpec.methodBuilder("customInject") 233 .addAnnotation(Override.class) 234 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 235 .addStatement("hiltInternalInject()") 236 .build(); 237 } 238 239 // // This is a known unsafe cast but is safe in the only correct use case: 240 // // $APP extends Hilt_$APP 241 // generatedComponent().inject$APP(($APP) this); injectCodeBlock()242 private CodeBlock injectCodeBlock() { 243 return CodeBlock.builder() 244 .add("// This is a known unsafe cast, but is safe in the only correct use case:\n") 245 .add("// $T extends $T\n", metadata.elementClassName(), metadata.generatedClassName()) 246 .addStatement( 247 "(($T) generatedComponent()).$L($L)", 248 metadata.injectorClassName(), 249 metadata.injectMethodName(), 250 Generators.unsafeCastThisTo(metadata.elementClassName())) 251 .build(); 252 } 253 } 254