• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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