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