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 
20 import com.squareup.javapoet.ClassName;
21 import com.squareup.javapoet.CodeBlock;
22 import com.squareup.javapoet.FieldSpec;
23 import com.squareup.javapoet.JavaFile;
24 import com.squareup.javapoet.MethodSpec;
25 import com.squareup.javapoet.ParameterSpec;
26 import com.squareup.javapoet.TypeName;
27 import com.squareup.javapoet.TypeSpec;
28 import com.squareup.javapoet.TypeVariableName;
29 import dagger.hilt.android.processor.internal.AndroidClassNames;
30 import dagger.hilt.processor.internal.ComponentNames;
31 import dagger.hilt.processor.internal.Processors;
32 import java.io.IOException;
33 import javax.annotation.processing.ProcessingEnvironment;
34 import javax.lang.model.element.Modifier;
35 
36 /** Generates an Hilt Application for an @AndroidEntryPoint app class. */
37 public final class ApplicationGenerator {
38   private final ProcessingEnvironment env;
39   private final AndroidEntryPointMetadata metadata;
40   private final ClassName wrapperClassName;
41 
ApplicationGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata)42   public ApplicationGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
43     this.env = env;
44     this.metadata = metadata;
45     wrapperClassName = metadata.generatedClassName();
46   }
47 
48   // @Generated("ApplicationGenerator")
49   // abstract class Hilt_$APP extends $BASE implements ComponentManager<ApplicationComponent> {
50   //   ...
51   // }
generate()52   public void generate() throws IOException {
53     TypeSpec.Builder typeSpecBuilder =
54         TypeSpec.classBuilder(wrapperClassName.simpleName())
55             .addOriginatingElement(metadata.element())
56             .superclass(metadata.baseClassName())
57             .addModifiers(metadata.generatedClassModifiers())
58             .addField(componentManagerField())
59             .addMethod(componentManagerMethod());
60 
61     Generators.addGeneratedBaseClassJavadoc(typeSpecBuilder, AndroidClassNames.HILT_ANDROID_APP);
62     Processors.addGeneratedAnnotation(typeSpecBuilder, env, getClass());
63 
64     metadata.baseElement().getTypeParameters().stream()
65         .map(TypeVariableName::get)
66         .forEachOrdered(typeSpecBuilder::addTypeVariable);
67 
68     Generators.copyLintAnnotations(metadata.element(), typeSpecBuilder);
69     Generators.addComponentOverride(metadata, typeSpecBuilder);
70 
71       typeSpecBuilder.addMethod(onCreateMethod());
72 
73     JavaFile.builder(metadata.elementClassName().packageName(), typeSpecBuilder.build())
74         .build()
75         .writeTo(env.getFiler());
76   }
77 
78   // private final ApplicationComponentManager<ApplicationComponent> componentManager =
79   //     new ApplicationComponentManager(/* creatorType */);
componentManagerField()80   private FieldSpec componentManagerField() {
81     ParameterSpec managerParam = metadata.componentManagerParam();
82     return FieldSpec.builder(managerParam.type, managerParam.name)
83         .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
84         .initializer("new $T($L)", AndroidClassNames.APPLICATION_COMPONENT_MANAGER, creatorType())
85         .build();
86   }
87 
88   // protected ApplicationComponentManager<ApplicationComponent> componentManager() {
89   //   return componentManager();
90   // }
componentManagerMethod()91   private MethodSpec componentManagerMethod() {
92     return MethodSpec.methodBuilder("componentManager")
93         .addAnnotation(Override.class)
94         .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
95         .returns(metadata.componentManagerParam().type)
96         .addStatement("return $N", metadata.componentManagerParam())
97         .build();
98   }
99 
100   // new Supplier<ApplicationComponent>() {
101   //   @Override
102   //   public ApplicationComponent get() {
103   //     return DaggerApplicationComponent.builder()
104   //         .applicationContextModule(new ApplicationContextModule(Hilt_$APP.this))
105   //         .build();
106   //   }
107   // }
creatorType()108   private TypeSpec creatorType() {
109     ClassName component =
110         ComponentNames.generatedComponent(
111             metadata.elementClassName(), AndroidClassNames.SINGLETON_COMPONENT);
112     return TypeSpec.anonymousClassBuilder("")
113         .addSuperinterface(AndroidClassNames.COMPONENT_SUPPLIER)
114         .addMethod(
115             MethodSpec.methodBuilder("get")
116                 .addAnnotation(Override.class)
117                 .addModifiers(Modifier.PUBLIC)
118                 .returns(TypeName.OBJECT)
119                 .addStatement(
120                     "return $T.builder()\n"
121                         + ".applicationContextModule(new $T($T.this))\n"
122                         + ".build()",
123                     Processors.prepend(Processors.getEnclosedClassName(component), "Dagger"),
124                     AndroidClassNames.APPLICATION_CONTEXT_MODULE,
125                     wrapperClassName)
126                 .build())
127         .build();
128   }
129 
130   // @CallSuper
131   // @Override
132   // public void onCreate() {
133   //   // This is a known unsafe cast but should be fine if the only use is
134   //   // $APP extends Hilt_$APP
135   //   generatedComponent().inject(($APP) this);
136   //   super.onCreate();
137   // }
onCreateMethod()138   private MethodSpec onCreateMethod() {
139     return MethodSpec.methodBuilder("onCreate")
140         .addAnnotation(AndroidClassNames.CALL_SUPER)
141         .addAnnotation(Override.class)
142         .addModifiers(Modifier.PUBLIC)
143         .addCode(injectCodeBlock())
144         .addStatement("super.onCreate()")
145         .build();
146   }
147 
148   //   // This is a known unsafe cast but should be fine if the only use is
149   //   // $APP extends Hilt_$APP
150   //   generatedComponent().inject$APP(($APP) this);
injectCodeBlock()151   private CodeBlock injectCodeBlock() {
152     return CodeBlock.builder()
153         .add("// This is a known unsafe cast, but is safe in the only correct use case:\n")
154         .add("// $T extends $T\n", metadata.elementClassName(), metadata.generatedClassName())
155         .addStatement(
156             "(($T) generatedComponent()).$L($L)",
157             metadata.injectorClassName(),
158             metadata.injectMethodName(),
159             Generators.unsafeCastThisTo(metadata.elementClassName()))
160         .build();
161   }
162 }
163