• 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.processor.internal;
18 
19 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils.getMetadataUtil;
22 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
24 import static javax.lang.model.element.Modifier.PUBLIC;
25 
26 import androidx.room.compiler.processing.JavaPoetExtKt;
27 import androidx.room.compiler.processing.XAnnotation;
28 import androidx.room.compiler.processing.XAnnotationValue;
29 import androidx.room.compiler.processing.XConstructorElement;
30 import androidx.room.compiler.processing.XElement;
31 import androidx.room.compiler.processing.XExecutableElement;
32 import androidx.room.compiler.processing.XFiler.Mode;
33 import androidx.room.compiler.processing.XHasModifiers;
34 import androidx.room.compiler.processing.XProcessingEnv;
35 import androidx.room.compiler.processing.XType;
36 import androidx.room.compiler.processing.XTypeElement;
37 import com.google.common.base.CaseFormat;
38 import com.google.common.base.Joiner;
39 import com.google.common.base.Preconditions;
40 import com.google.common.collect.ImmutableList;
41 import com.google.common.collect.ImmutableMap;
42 import com.google.common.collect.ImmutableSet;
43 import com.squareup.javapoet.AnnotationSpec;
44 import com.squareup.javapoet.ClassName;
45 import com.squareup.javapoet.JavaFile;
46 import com.squareup.javapoet.MethodSpec;
47 import com.squareup.javapoet.ParameterizedTypeName;
48 import com.squareup.javapoet.TypeName;
49 import com.squareup.javapoet.TypeSpec;
50 import dagger.internal.codegen.xprocessing.XAnnotations;
51 import dagger.internal.codegen.xprocessing.XElements;
52 import dagger.internal.codegen.xprocessing.XTypes;
53 import java.util.List;
54 import java.util.Objects;
55 import java.util.Optional;
56 import javax.lang.model.element.AnnotationMirror;
57 import javax.lang.model.element.Element;
58 
59 /** Static helper methods for writing a processor. */
60 public final class Processors {
61 
62   public static final String CONSTRUCTOR_NAME = "<init>";
63 
64   public static final String STATIC_INITIALIZER_NAME = "<clinit>";
65 
66   /** Generates the aggregating metadata class for an aggregating annotation. */
generateAggregatingClass( String aggregatingPackage, AnnotationSpec aggregatingAnnotation, XTypeElement originatingElement, Class<?> generatorClass)67   public static void generateAggregatingClass(
68       String aggregatingPackage,
69       AnnotationSpec aggregatingAnnotation,
70       XTypeElement originatingElement,
71       Class<?> generatorClass) {
72     generateAggregatingClass(
73         aggregatingPackage,
74         aggregatingAnnotation,
75         originatingElement,
76         generatorClass,
77         Mode.Isolating);
78   }
79 
80   /** Generates the aggregating metadata class for an aggregating annotation. */
generateAggregatingClass( String aggregatingPackage, AnnotationSpec aggregatingAnnotation, XTypeElement originatingElement, Class<?> generatorClass, Mode mode)81   public static void generateAggregatingClass(
82       String aggregatingPackage,
83       AnnotationSpec aggregatingAnnotation,
84       XTypeElement originatingElement,
85       Class<?> generatorClass,
86       Mode mode) {
87     ClassName name =
88         ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(originatingElement));
89     XProcessingEnv env = getProcessingEnv(originatingElement);
90     TypeSpec.Builder builder =
91         TypeSpec.classBuilder(name)
92             .addModifiers(PUBLIC)
93             .addAnnotation(aggregatingAnnotation)
94             .addJavadoc("This class should only be referenced by generated code! ")
95             .addJavadoc("This class aggregates information across multiple compilations.\n");
96     JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
97     addGeneratedAnnotation(builder, env, generatorClass);
98 
99     env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode);
100   }
101 
102   /** Returns a map from {@link XAnnotation} attribute name to {@link XAnnotationValue}s */
getAnnotationValues(XAnnotation annotation)103   public static ImmutableMap<String, XAnnotationValue> getAnnotationValues(XAnnotation annotation) {
104     ImmutableMap.Builder<String, XAnnotationValue> annotationMembers = ImmutableMap.builder();
105     for (XAnnotationValue value : annotation.getAnnotationValues()) {
106       annotationMembers.put(value.getName(), value);
107     }
108     return annotationMembers.build();
109   }
110 
111   /** Returns a list of {@link XTypeElement}s for a class attribute on an annotation. */
getAnnotationClassValues( XAnnotation annotation, String key)112   public static ImmutableList<XTypeElement> getAnnotationClassValues(
113       XAnnotation annotation, String key) {
114     ImmutableList<XTypeElement> values = XAnnotations.getAsTypeElementList(annotation, key);
115 
116     ProcessorErrors.checkState(
117         values.size() >= 1,
118         annotation.getTypeElement(),
119         "@%s, '%s' class is invalid or missing: %s",
120         annotation.getName(),
121         key,
122         XAnnotations.toStableString(annotation));
123 
124     return values;
125   }
126 
getOptionalAnnotationClassValues( XAnnotation annotation, String key)127   public static ImmutableList<XTypeElement> getOptionalAnnotationClassValues(
128       XAnnotation annotation, String key) {
129     return getOptionalAnnotationValues(annotation, key).stream()
130         .filter(XAnnotationValue::hasTypeValue)
131         .map(
132             annotationValue -> {
133               try {
134                 return annotationValue.asType();
135               } catch (TypeNotPresentException e) {
136                 // TODO(b/277367118): we may need a way to ignore error types in XProcessing.
137                 // TODO(b/278560196): we should throw ErrorTypeException and clean up broken tests.
138                 return null;
139               }
140             })
141         .filter(Objects::nonNull)
142         .map(XType::getTypeElement)
143         .collect(toImmutableList());
144   }
145 
146   private static ImmutableList<XAnnotationValue> getOptionalAnnotationValues(
147       XAnnotation annotation, String key) {
148     return annotation.getAnnotationValues().stream()
149         .filter(annotationValue -> annotationValue.getName().equals(key))
150         .collect(toOptional())
151         .map(
152             annotationValue ->
153                 (annotationValue.hasListValue()
154                     ? ImmutableList.copyOf(annotationValue.asAnnotationValueList())
155                     : ImmutableList.of(annotationValue)))
156         .orElse(ImmutableList.of());
157   }
158 
159   public static XTypeElement getTopLevelType(XElement originalElement) {
160     checkNotNull(originalElement);
161     for (XElement e = originalElement; e != null; e = e.getEnclosingElement()) {
162       if (isTopLevel(e)) {
163         return XElements.asTypeElement(e);
164       }
165     }
166     throw new IllegalStateException(
167         "Cannot find a top-level type for " + XElements.toStableString(originalElement));
168   }
169 
170   public static boolean isTopLevel(XElement element) {
171     return element.getEnclosingElement() == null;
172   }
173 
174   /** Returns true if the given element has an annotation with the given class name. */
175   public static boolean hasAnnotation(Element element, ClassName className) {
176     return getAnnotationMirrorOptional(element, className).isPresent();
177   }
178 
179   /** Returns true if the given element has an annotation that is an error kind. */
180   public static boolean hasErrorTypeAnnotation(XElement element) {
181     for (XAnnotation annotation : element.getAllAnnotations()) {
182       if (annotation.getType().isError()) {
183         return true;
184       }
185     }
186     return false;
187   }
188 
189   /**
190    * Returns the annotation mirror from the given element that corresponds to the given class.
191    *
192    * @throws IllegalArgumentException if 2 or more annotations are found.
193    * @return {@link Optional#empty()} if no annotation is found on the element.
194    */
195   static Optional<AnnotationMirror> getAnnotationMirrorOptional(
196       Element element, ClassName className) {
197     return element.getAnnotationMirrors().stream()
198         .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className))
199         .collect(toOptional());
200   }
201 
202   /**
203    * Returns the name of a class, including prefixing with enclosing class names. i.e. for inner
204    * class Foo enclosed by Bar, returns Bar_Foo instead of just Foo
205    */
206   public static String getEnclosedName(ClassName name) {
207     return Joiner.on('_').join(name.simpleNames());
208   }
209 
210   /**
211    * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
212    * {@code _}.
213    */
214   public static ClassName getEnclosedClassName(ClassName className) {
215     return ClassName.get(className.packageName(), getEnclosedName(className));
216   }
217 
218   /**
219    * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
220    * {@code _}.
221    */
222   public static ClassName getEnclosedClassName(XTypeElement typeElement) {
223     return getEnclosedClassName(typeElement.getClassName());
224   }
225 
226   /**
227    * Returns the fully qualified class name, with _ instead of . For elements that are not type
228    * elements, this continues to append the simple name of elements. For example,
229    * foo_bar_Outer_Inner_fooMethod.
230    */
231   public static String getFullEnclosedName(XElement element) {
232     Preconditions.checkNotNull(element);
233     String qualifiedName = "";
234     while (element != null) {
235       if (element.getEnclosingElement() == null) {
236         qualifiedName =
237             element.getClosestMemberContainer().asClassName().getCanonicalName() + qualifiedName;
238       } else {
239         // This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11
240         // contains newly added "module" enclosing elements of packages, which adds an additional
241         // "_" prefix to the name due to an empty module element compared with jdk8.
242         if (!XElements.getSimpleName(element).isEmpty()) {
243           qualifiedName = "." + XElements.getSimpleName(element) + qualifiedName;
244         }
245       }
246       element = element.getEnclosingElement();
247     }
248     return qualifiedName.replace('.', '_');
249   }
250 
251   /** Appends the given string to the end of the class name. */
252   public static ClassName append(ClassName name, String suffix) {
253     return name.peerClass(name.simpleName() + suffix);
254   }
255 
256   /** Prepends the given string to the beginning of the class name. */
257   public static ClassName prepend(ClassName name, String prefix) {
258     return name.peerClass(prefix + name.simpleName());
259   }
260 
261   /**
262    * Removes the string {@code suffix} from the simple name of {@code type} and returns it.
263    *
264    * @throws BadInputException if the simple name of {@code type} does not end with {@code suffix}
265    */
266   public static ClassName removeNameSuffix(XTypeElement type, String suffix) {
267     ClassName originalName = type.getClassName();
268     String originalSimpleName = originalName.simpleName();
269     ProcessorErrors.checkState(
270         originalSimpleName.endsWith(suffix),
271         type,
272         "Name of type %s must end with '%s'",
273         originalName,
274         suffix);
275     String withoutSuffix =
276         originalSimpleName.substring(0, originalSimpleName.length() - suffix.length());
277     return originalName.peerClass(withoutSuffix);
278   }
279 
280   /** Returns {@code true} if element inherits directly or indirectly from the className. */
281   public static boolean isAssignableFrom(XTypeElement element, ClassName className) {
282     return isAssignableFromAnyOf(element, ImmutableSet.of(className));
283   }
284 
285   /** Returns {@code true} if element inherits directly or indirectly from any of the classNames. */
286   public static boolean isAssignableFromAnyOf(
287       XTypeElement element, ImmutableSet<ClassName> classNames) {
288     for (ClassName className : classNames) {
289       if (element.getClassName().equals(className)) {
290         return true;
291       }
292     }
293 
294     XType superClass = element.getSuperClass();
295     // None type is returned if this is an interface or Object
296     // Error type is returned for classes that are generated by this processor
297     if (superClass != null && !superClass.isNone() && !superClass.isError()) {
298       Preconditions.checkState(XTypes.isDeclared(superClass));
299       if (isAssignableFromAnyOf(superClass.getTypeElement(), classNames)) {
300         return true;
301       }
302     }
303 
304     for (XType iface : element.getSuperInterfaces()) {
305       // Skip errors and keep looking. This is especially needed for classes generated by this
306       // processor.
307       if (iface.isError()) {
308         continue;
309       }
310       Preconditions.checkState(
311           XTypes.isDeclared(iface), "Interface type is %s", XTypes.getKindName(iface));
312       if (isAssignableFromAnyOf(iface.getTypeElement(), classNames)) {
313         return true;
314       }
315     }
316 
317     return false;
318   }
319 
320   /** Returns MapKey annotated annotations found on an element. */
321   public static ImmutableList<XAnnotation> getMapKeyAnnotations(XElement element) {
322     // Normally, we wouldn't need to handle Kotlin metadata because map keys are typically used
323     // only on methods. However, with @BindValueIntoMap, this can be used on fields so we need
324     // to check annotations on the property as well, just like with qualifiers.
325     return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.MAP_KEY);
326   }
327 
328   /** Returns Qualifier annotated annotations found on an element. */
329   public static ImmutableList<XAnnotation> getQualifierAnnotations(XElement element) {
330     return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.QUALIFIER);
331   }
332 
333   /** Returns Scope annotated annotations found on an element. */
334   public static ImmutableList<XAnnotation> getScopeAnnotations(XElement element) {
335     return ImmutableList.copyOf(
336         element.getAnnotationsAnnotatedWith(ClassNames.SCOPE));
337   }
338 
339   /**
340    * Shortcut for converting from upper camel to lower camel case
341    *
342    * <p>Example: "SomeString" => "someString"
343    */
344   public static String upperToLowerCamel(String upperCamel) {
345     return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel);
346   }
347 
348   /** @return copy of the given MethodSpec as {@link MethodSpec.Builder} with method body removed */
349   public static MethodSpec.Builder copyMethodSpecWithoutBody(MethodSpec methodSpec) {
350     MethodSpec.Builder builder;
351 
352     if (methodSpec.isConstructor()) {
353       // Constructors cannot have return types
354       builder = MethodSpec.constructorBuilder();
355     } else {
356       builder = MethodSpec.methodBuilder(methodSpec.name)
357           .returns(methodSpec.returnType);
358     }
359 
360     return builder
361         .addAnnotations(methodSpec.annotations)
362         .addModifiers(methodSpec.modifiers)
363         .addParameters(methodSpec.parameters)
364         .addExceptions(methodSpec.exceptions)
365         .addJavadoc(methodSpec.javadoc.toString())
366         .addTypeVariables(methodSpec.typeVariables);
367   }
368 
369   /**
370    * Returns true if the given method is annotated with one of the annotations Dagger recognizes for
371    * abstract methods (e.g. @Binds).
372    */
373   public static boolean hasDaggerAbstractMethodAnnotation(XExecutableElement method) {
374     return method.hasAnnotation(ClassNames.BINDS)
375         || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF)
376         || method.hasAnnotation(ClassNames.MULTIBINDS)
377         || method.hasAnnotation(ClassNames.CONTRIBUTES_ANDROID_INJECTOR);
378   }
379 
380   public static boolean requiresModuleInstance(XTypeElement module) {
381     // Binding methods that lack ABSTRACT or STATIC require module instantiation.
382     // Required by Dagger.  See b/31489617.
383     return module.getDeclaredMethods().stream()
384             .filter(Processors::isBindingMethod)
385             .anyMatch(method -> !method.isAbstract() && !method.isStatic())
386         && !module.isKotlinObject();
387   }
388 
389   public static boolean hasVisibleEmptyConstructor(XTypeElement type) {
390     List<XConstructorElement> constructors = type.getConstructors();
391     return constructors.isEmpty()
392         || constructors.stream()
393             .filter(constructor -> constructor.getParameters().isEmpty())
394             .anyMatch(
395                 constructor ->
396                     !constructor.isPrivate()
397                         );
398   }
399 
400   private static boolean isBindingMethod(XExecutableElement method) {
401     return method.hasAnnotation(ClassNames.PROVIDES)
402         || method.hasAnnotation(ClassNames.BINDS)
403         || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF)
404         || method.hasAnnotation(ClassNames.MULTIBINDS);
405   }
406 
407   public static void addGeneratedAnnotation(
408       TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, Class<?> generatorClass) {
409     addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName());
410   }
411 
412   public static void addGeneratedAnnotation(
413       TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, String generatorClass) {
414     XTypeElement annotation = env.findGeneratedAnnotation();
415     if (annotation != null) {
416       typeSpecBuilder.addAnnotation(
417           AnnotationSpec.builder(annotation.getClassName())
418               .addMember("value", "$S", generatorClass)
419               .build());
420     }
421   }
422 
423   public static AnnotationSpec getOriginatingElementAnnotation(XTypeElement element) {
424     TypeName rawType = rawTypeName(getTopLevelType(element).getClassName());
425     return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
426         .addMember("topLevelClass", "$T.class", rawType)
427         .build();
428   }
429 
430   /**
431    * Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a
432    * parameterized type, it returns the argument unchanged.
433    */
434   public static TypeName rawTypeName(TypeName typeName) {
435     return (typeName instanceof ParameterizedTypeName)
436         ? ((ParameterizedTypeName) typeName).rawType
437         : typeName;
438   }
439 
440   public static Optional<XTypeElement> getOriginatingTestElement(XElement element) {
441     XTypeElement topLevelType = getOriginatingTopLevelType(element);
442     return topLevelType.hasAnnotation(ClassNames.HILT_ANDROID_TEST)
443         ? Optional.of(topLevelType)
444         : Optional.empty();
445   }
446 
447   private static XTypeElement getOriginatingTopLevelType(XElement element) {
448     XTypeElement topLevelType = getTopLevelType(element);
449     if (topLevelType.hasAnnotation(ClassNames.ORIGINATING_ELEMENT)) {
450       return getOriginatingTopLevelType(
451           XAnnotations.getAsTypeElement(
452               topLevelType.getAnnotation(ClassNames.ORIGINATING_ELEMENT), "topLevelClass"));
453     }
454     return topLevelType;
455   }
456 
457   public static boolean hasJavaPackagePrivateVisibility(XHasModifiers element) {
458     return !element.isPrivate()
459         && !element.isProtected()
460         && !element.isInternal()
461         && !element.isPublic();
462   }
463 
464   private Processors() {}
465 }
466