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 com.google.auto.common.MoreElements;
20 import com.google.auto.common.Visibility;
21 import com.squareup.javapoet.AnnotationSpec;
22 import com.squareup.javapoet.ClassName;
23 import com.squareup.javapoet.JavaFile;
24 import com.squareup.javapoet.MethodSpec;
25 import com.squareup.javapoet.TypeSpec;
26 import com.squareup.javapoet.TypeVariableName;
27 import dagger.hilt.android.processor.internal.AndroidClassNames;
28 import dagger.hilt.processor.internal.Processors;
29 import java.io.IOException;
30 import java.util.List;
31 import javax.annotation.processing.ProcessingEnvironment;
32 import javax.lang.model.element.Element;
33 import javax.lang.model.element.ExecutableElement;
34 import javax.lang.model.element.TypeElement;
35 import javax.lang.model.element.VariableElement;
36 import javax.lang.model.type.TypeKind;
37 import javax.lang.model.type.TypeMirror;
38 import javax.lang.model.util.ElementFilter;
39 
40 /** Generates an Hilt View class for the @AndroidEntryPoint annotated class. */
41 public final class ViewGenerator {
42   private final ProcessingEnvironment env;
43   private final AndroidEntryPointMetadata metadata;
44   private final ClassName generatedClassName;
45 
ViewGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata)46   public ViewGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
47     this.env = env;
48     this.metadata = metadata;
49 
50     generatedClassName = metadata.generatedClassName();
51   }
52 
53   // @Generated("ViewGenerator")
54   // abstract class Hilt_$CLASS extends $BASE implements
55   //    ComponentManagerHolder<ViewComponentManager<$CLASS_EntryPoint>> {
56   //   ...
57   // }
generate()58   public void generate() throws IOException {
59     // Note: we do not use the Generators helper methods here because injection is called
60     // from the constructor where the double-check pattern doesn't work (due to the super
61     // constructor being called before fields are initialized) and because it isn't necessary
62     // since the object isn't done constructing yet.
63 
64     TypeSpec.Builder builder =
65         TypeSpec.classBuilder(generatedClassName.simpleName())
66             .addOriginatingElement(metadata.element())
67             .superclass(metadata.baseClassName())
68             .addModifiers(metadata.generatedClassModifiers());
69 
70     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
71     Processors.addGeneratedAnnotation(builder, env, getClass());
72     Generators.copyLintAnnotations(metadata.element(), builder);
73 
74     metadata.baseElement().getTypeParameters().stream()
75         .map(TypeVariableName::get)
76         .forEachOrdered(builder::addTypeVariable);
77 
78     Generators.addComponentOverride(metadata, builder);
79 
80     Generators.addInjectionMethods(metadata, builder);
81 
82     ElementFilter.constructorsIn(metadata.baseElement().getEnclosedElements()).stream()
83         .filter(this::isConstructorVisibleToGeneratedClass)
84         .forEach(constructor -> builder.addMethod(constructorMethod(constructor)));
85 
86     JavaFile.builder(generatedClassName.packageName(), builder.build())
87         .build()
88         .writeTo(env.getFiler());
89   }
90 
isConstructorVisibleToGeneratedClass(ExecutableElement constructorElement)91   private boolean isConstructorVisibleToGeneratedClass(ExecutableElement constructorElement) {
92     if (Visibility.ofElement(constructorElement) == Visibility.DEFAULT
93         && !isInOurPackage(constructorElement)) {
94       return false;
95     } else if (Visibility.ofElement(constructorElement) == Visibility.PRIVATE) {
96       return false;
97     }
98 
99     // We extend the base class, so both protected and public methods are always accessible.
100     return true;
101   }
102 
103   /**
104    * Returns a pass-through constructor matching the base class's provided constructorElement. The
105    * generated constructor simply calls super(), then inject().
106    *
107    * <p>Eg
108    *
109    * <pre>
110    *   Hilt_$CLASS(Context context, ...) {
111    *     super(context, ...);
112    *     inject();
113    *   }
114    * </pre>
115    */
constructorMethod(ExecutableElement constructorElement)116   private MethodSpec constructorMethod(ExecutableElement constructorElement) {
117     MethodSpec.Builder constructor =
118         Generators.copyConstructor(constructorElement).toBuilder();
119 
120     if (isRestrictedApiConstructor(constructorElement)) {
121       // 4 parameter constructors are only available on @TargetApi(21).
122       constructor.addAnnotation(
123           AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build());
124     }
125 
126     constructor.addStatement("inject()");
127 
128     return constructor.build();
129   }
130 
isRestrictedApiConstructor(ExecutableElement constructor)131   private boolean isRestrictedApiConstructor(ExecutableElement constructor) {
132     if (constructor.getParameters().size() != 4) {
133       return false;
134     }
135 
136     List<? extends VariableElement> constructorParams = constructor.getParameters();
137     for (int i = 0; i < constructorParams.size(); i++) {
138       TypeMirror type = constructorParams.get(i).asType();
139       Element element = env.getTypeUtils().asElement(type);
140       switch (i) {
141         case 0:
142           if (!isFirstRestrictedParameter(element)) {
143             return false;
144           }
145           break;
146         case 1:
147           if (!isSecondRestrictedParameter(element)) {
148             return false;
149           }
150           break;
151         case 2:
152           if (!isThirdRestrictedParameter(type)) {
153             return false;
154           }
155           break;
156         case 3:
157           if (!isFourthRestrictedParameter(type)) {
158             return false;
159           }
160           break;
161         default:
162           return false;
163       }
164     }
165 
166     return true;
167   }
168 
isFourthRestrictedParameter(TypeMirror type)169   private static boolean isFourthRestrictedParameter(TypeMirror type) {
170     return type.getKind().isPrimitive()
171         && Processors.getPrimitiveType(type).getKind() == TypeKind.INT;
172   }
173 
isThirdRestrictedParameter(TypeMirror type)174   private static boolean isThirdRestrictedParameter(TypeMirror type) {
175     return type.getKind().isPrimitive()
176         && Processors.getPrimitiveType(type).getKind() == TypeKind.INT;
177   }
178 
isSecondRestrictedParameter(Element element)179   private static boolean isSecondRestrictedParameter(Element element) {
180     return element instanceof TypeElement
181         && Processors.isAssignableFrom(
182             MoreElements.asType(element), AndroidClassNames.ATTRIBUTE_SET);
183   }
184 
isFirstRestrictedParameter(Element element)185   private static boolean isFirstRestrictedParameter(Element element) {
186     return element instanceof TypeElement
187         && Processors.isAssignableFrom(MoreElements.asType(element), AndroidClassNames.CONTEXT);
188   }
189 
isInOurPackage(ExecutableElement constructorElement)190   private boolean isInOurPackage(ExecutableElement constructorElement) {
191     return MoreElements.getPackage(constructorElement)
192         .equals(MoreElements.getPackage(metadata.element()));
193   }
194 }
195