• 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 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