• 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(this::isConstructorVisibleToGeneratedClass)
82         .map(this::constructorMethod)
83         .forEach(builder::addMethod);
84 
85     env.getFiler()
86         .write(
87             JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
88             XFiler.Mode.Isolating);
89   }
90 
isConstructorVisibleToGeneratedClass(XConstructorElement constructor)91   private boolean isConstructorVisibleToGeneratedClass(XConstructorElement constructor) {
92     if (Processors.hasJavaPackagePrivateVisibility(constructor) && !isInOurPackage(constructor)) {
93       return false;
94     } else if (constructor.isPrivate()) {
95       return false;
96     }
97 
98     // We extend the base class, so both protected and public methods are always accessible.
99     return true;
100   }
101 
102   /**
103    * Returns a pass-through constructor matching the base class's provided constructorElement. The
104    * generated constructor simply calls super(), then inject().
105    *
106    * <p>Eg
107    *
108    * <pre>
109    *   Hilt_$CLASS(Context context, ...) {
110    *     super(context, ...);
111    *     inject();
112    *   }
113    * </pre>
114    */
constructorMethod(XConstructorElement constructor)115   private MethodSpec constructorMethod(XConstructorElement constructor) {
116     MethodSpec.Builder builder = Generators.copyConstructor(constructor).toBuilder();
117 
118     // TODO(b/210544481): Once this bug is fixed we should require that the user adds this
119     // annotation to their constructor and we'll propagate it from there rather than trying to
120     // guess whether this needs @TargetApi from the signature. This check is a bit flawed. For
121     // example, the user could write a 5 parameter constructor that calls the restricted 4 parameter
122     // constructor and we would miss adding @TargetApi to it.
123     if (isRestrictedApiConstructor(constructor)) {
124       // 4 parameter constructors are only available on @TargetApi(21).
125       builder.addAnnotation(
126           AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build());
127     }
128 
129     builder.addStatement("inject()");
130 
131     return builder.build();
132   }
133 
isRestrictedApiConstructor(XConstructorElement constructor)134   private boolean isRestrictedApiConstructor(XConstructorElement constructor) {
135     if (constructor.getParameters().size() != 4) {
136       return false;
137     }
138 
139     List<XExecutableParameterElement> constructorParams = constructor.getParameters();
140     for (int i = 0; i < constructorParams.size(); i++) {
141       XType type = constructorParams.get(i).getType();
142       switch (i) {
143         case 0:
144           if (!isFirstRestrictedParameter(type)) {
145             return false;
146           }
147           break;
148         case 1:
149           if (!isSecondRestrictedParameter(type)) {
150             return false;
151           }
152           break;
153         case 2:
154           if (!isThirdRestrictedParameter(type)) {
155             return false;
156           }
157           break;
158         case 3:
159           if (!isFourthRestrictedParameter(type)) {
160             return false;
161           }
162           break;
163         default:
164           return false;
165       }
166     }
167 
168     return true;
169   }
170 
isFourthRestrictedParameter(XType type)171   private static boolean isFourthRestrictedParameter(XType type) {
172     return isPrimitive(type) && isInt(type);
173   }
174 
isThirdRestrictedParameter(XType type)175   private static boolean isThirdRestrictedParameter(XType type) {
176     return isPrimitive(type) && isInt(type);
177   }
178 
isSecondRestrictedParameter(XType type)179   private static boolean isSecondRestrictedParameter(XType type) {
180     return isDeclared(type)
181         && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.ATTRIBUTE_SET);
182   }
183 
isFirstRestrictedParameter(XType type)184   private static boolean isFirstRestrictedParameter(XType type) {
185     return isDeclared(type)
186         && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.CONTEXT);
187   }
188 
isInOurPackage(XConstructorElement constructor)189   private boolean isInOurPackage(XConstructorElement constructor) {
190     return constructor
191         .getEnclosingElement()
192         .getPackageName()
193         .contentEquals(metadata.element().getPackageName());
194   }
195 }
196