• 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 androidx.room.compiler.processing.XTypeKt.isInt;
20 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
21 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
22 
23 import androidx.room.compiler.processing.JavaPoetExtKt;
24 import androidx.room.compiler.processing.XConstructorElement;
25 import androidx.room.compiler.processing.XExecutableParameterElement;
26 import androidx.room.compiler.processing.XFiler;
27 import androidx.room.compiler.processing.XProcessingEnv;
28 import androidx.room.compiler.processing.XType;
29 import androidx.room.compiler.processing.XTypeParameterElement;
30 import com.squareup.javapoet.AnnotationSpec;
31 import com.squareup.javapoet.ClassName;
32 import com.squareup.javapoet.JavaFile;
33 import com.squareup.javapoet.MethodSpec;
34 import com.squareup.javapoet.TypeSpec;
35 import dagger.hilt.android.processor.internal.AndroidClassNames;
36 import dagger.hilt.processor.internal.Processors;
37 import java.util.List;
38 
39 /** Generates an Hilt View class for the @AndroidEntryPoint annotated class. */
40 public final class ViewGenerator {
41   private final XProcessingEnv env;
42   private final AndroidEntryPointMetadata metadata;
43   private final ClassName generatedClassName;
44 
ViewGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)45   public ViewGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
46     this.env = env;
47     this.metadata = metadata;
48     generatedClassName = metadata.generatedClassName();
49   }
50 
51   // @Generated("ViewGenerator")
52   // abstract class Hilt_$CLASS extends $BASE implements
53   //    ComponentManagerHolder<ViewComponentManager<$CLASS_EntryPoint>> {
54   //   ...
55   // }
generate()56   public void generate() {
57     // Note: we do not use the Generators helper methods here because injection is called
58     // from the constructor where the double-check pattern doesn't work (due to the super
59     // constructor being called before fields are initialized) and because it isn't necessary
60     // since the object isn't done constructing yet.
61 
62     TypeSpec.Builder builder =
63         TypeSpec.classBuilder(generatedClassName.simpleName())
64             .superclass(metadata.baseClassName())
65             .addModifiers(metadata.generatedClassModifiers());
66 
67     JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
68     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
69     Processors.addGeneratedAnnotation(builder, env, getClass());
70     Generators.copyLintAnnotations(metadata.element(), builder);
71     Generators.copySuppressAnnotations(metadata.element(), builder);
72 
73     metadata.baseElement().getTypeParameters().stream()
74         .map(XTypeParameterElement::getTypeVariableName)
75         .forEachOrdered(builder::addTypeVariable);
76 
77     Generators.addComponentOverride(metadata, builder);
78     Generators.addInjectionMethods(metadata, builder);
79 
80     metadata.baseElement().getConstructors().stream()
81         .filter(constructor -> Generators.isConstructorVisibleToSubclass(
82             constructor, metadata.element()))
83         .map(this::constructorMethod)
84         .forEach(builder::addMethod);
85 
86     env.getFiler()
87         .write(
88             JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
89             XFiler.Mode.Isolating);
90   }
91 
92   /**
93    * Returns a pass-through constructor matching the base class's provided constructorElement. The
94    * generated constructor simply calls super(), then inject().
95    *
96    * <p>Eg
97    *
98    * <pre>
99    *   Hilt_$CLASS(Context context, ...) {
100    *     super(context, ...);
101    *     if (!isInEditMode()) {
102    *       inject();
103    *     }
104    *   }
105    * </pre>
106    */
constructorMethod(XConstructorElement constructor)107   private MethodSpec constructorMethod(XConstructorElement constructor) {
108     MethodSpec.Builder builder = Generators.copyConstructor(constructor).toBuilder();
109 
110     // TODO(b/210544481): Once this bug is fixed we should require that the user adds this
111     // annotation to their constructor and we'll propagate it from there rather than trying to
112     // guess whether this needs @TargetApi from the signature. This check is a bit flawed. For
113     // example, the user could write a 5 parameter constructor that calls the restricted 4 parameter
114     // constructor and we would miss adding @TargetApi to it.
115     if (isRestrictedApiConstructor(constructor)) {
116       // 4 parameter constructors are only available on @TargetApi(21).
117       builder.addAnnotation(
118           AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build());
119     }
120 
121     builder.beginControlFlow("if(!isInEditMode())")
122         .addStatement("inject()")
123         .endControlFlow();
124 
125     return builder.build();
126   }
127 
isRestrictedApiConstructor(XConstructorElement constructor)128   private boolean isRestrictedApiConstructor(XConstructorElement constructor) {
129     if (constructor.getParameters().size() != 4) {
130       return false;
131     }
132 
133     List<XExecutableParameterElement> constructorParams = constructor.getParameters();
134     for (int i = 0; i < constructorParams.size(); i++) {
135       XType type = constructorParams.get(i).getType();
136       switch (i) {
137         case 0:
138           if (!isFirstRestrictedParameter(type)) {
139             return false;
140           }
141           break;
142         case 1:
143           if (!isSecondRestrictedParameter(type)) {
144             return false;
145           }
146           break;
147         case 2:
148           if (!isThirdRestrictedParameter(type)) {
149             return false;
150           }
151           break;
152         case 3:
153           if (!isFourthRestrictedParameter(type)) {
154             return false;
155           }
156           break;
157         default:
158           return false;
159       }
160     }
161 
162     return true;
163   }
164 
isFourthRestrictedParameter(XType type)165   private static boolean isFourthRestrictedParameter(XType type) {
166     return isPrimitive(type) && isInt(type);
167   }
168 
isThirdRestrictedParameter(XType type)169   private static boolean isThirdRestrictedParameter(XType type) {
170     return isPrimitive(type) && isInt(type);
171   }
172 
isSecondRestrictedParameter(XType type)173   private static boolean isSecondRestrictedParameter(XType type) {
174     return isDeclared(type)
175         && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.ATTRIBUTE_SET);
176   }
177 
isFirstRestrictedParameter(XType type)178   private static boolean isFirstRestrictedParameter(XType type) {
179     return isDeclared(type)
180         && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.CONTEXT);
181   }
182 }
183