/* * Copyright (C) 2019 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.hilt.processor.internal; import static com.google.auto.common.MoreElements.asPackage; import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreElements.asVariable; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.AnnotationMirrors; import com.google.auto.common.GeneratedAnnotations; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.base.CaseFormat; import com.google.common.base.Equivalence.Wrapper; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.extension.DaggerStreams; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import java.io.IOException; import java.lang.annotation.Annotation; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ErrorType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleAnnotationValueVisitor7; import javax.lang.model.util.SimpleTypeVisitor7; /** Static helper methods for writing a processor. */ public final class Processors { public static final String CONSTRUCTOR_NAME = ""; public static final String STATIC_INITIALIZER_NAME = ""; private static final String JAVA_CLASS = "java.lang.Class"; public static void generateAggregatingClass( String aggregatingPackage, AnnotationSpec aggregatingAnnotation, TypeElement element, Class generatedAnnotationClass, ProcessingEnvironment env) throws IOException { ClassName name = ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(element)); TypeSpec.Builder builder = TypeSpec.classBuilder(name) .addModifiers(PUBLIC) .addOriginatingElement(element) .addAnnotation(aggregatingAnnotation) .addJavadoc("This class should only be referenced by generated code!") .addJavadoc("This class aggregates information across multiple compilations.\n");; addGeneratedAnnotation(builder, env, generatedAnnotationClass); JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler()); } /** Returns a map from {@link AnnotationMirror} attribute name to {@link AnnotationValue}s */ public static ImmutableMap getAnnotationValues(Elements elements, AnnotationMirror annotation) { ImmutableMap.Builder annotationMembers = ImmutableMap.builder(); for (Map.Entry e : elements.getElementValuesWithDefaults(annotation).entrySet()) { annotationMembers.put(e.getKey().getSimpleName().toString(), e.getValue()); } return annotationMembers.build(); } /** * Returns a multimap from attribute name to the values that are an array of annotation mirrors. * The returned map will not contain mappings for any attributes that are not Annotation Arrays. * *

e.g. if the input was the annotation mirror for *

   *   {@literal @}Foo({{@literal @}Bar("hello"), {@literal @}Bar("world")})
   * 
* the map returned would have "value" map to a set containing the two @Bar annotation mirrors. */ public static Multimap getAnnotationAnnotationArrayValues( Elements elements, AnnotationMirror annotation) { SetMultimap annotationMembers = LinkedHashMultimap.create(); for (Map.Entry e : elements.getElementValuesWithDefaults(annotation).entrySet()) { String attribute = e.getKey().getSimpleName().toString(); Set annotationMirrors = new LinkedHashSet<>(); e.getValue().accept(new AnnotationMirrorAnnotationValueVisitor(), annotationMirrors); annotationMembers.putAll(attribute, annotationMirrors); } return annotationMembers; } private static final class AnnotationMirrorAnnotationValueVisitor extends SimpleAnnotationValueVisitor7> { @Override public Void visitArray(List vals, Set types) { for (AnnotationValue val : vals) { val.accept(this, types); } return null; } @Override public Void visitAnnotation(AnnotationMirror a, Set annotationMirrors) { annotationMirrors.add(a); return null; } } /** Returns the {@link TypeElement} for a class attribute on an annotation. */ public static TypeElement getAnnotationClassValue( Elements elements, AnnotationMirror annotation, String key) { return Iterables.getOnlyElement(getAnnotationClassValues(elements, annotation, key)); } /** Returns a list of {@link TypeElement}s for a class attribute on an annotation. */ public static ImmutableList getAnnotationClassValues( Elements elements, AnnotationMirror annotation, String key) { ImmutableList values = getOptionalAnnotationClassValues(elements, annotation, key); ProcessorErrors.checkState( values.size() >= 1, // TODO(b/152801981): Point to the annotation value rather than the annotated element. annotation.getAnnotationType().asElement(), "@%s, '%s' class is invalid or missing: %s", annotation.getAnnotationType().asElement().getSimpleName(), key, annotation); return values; } /** Returns a multimap from attribute name to elements for class valued attributes. */ private static Multimap getAnnotationClassValues( Elements elements, AnnotationMirror annotation) { Element javaClass = elements.getTypeElement(JAVA_CLASS); SetMultimap annotationMembers = LinkedHashMultimap.create(); for (Map.Entry e : elements.getElementValuesWithDefaults(annotation).entrySet()) { Optional returnType = getOptionalDeclaredType(e.getKey().getReturnType()); if (returnType.isPresent() && returnType.get().asElement().equals(javaClass)) { String attribute = e.getKey().getSimpleName().toString(); Set declaredTypes = new LinkedHashSet(); e.getValue().accept(new DeclaredTypeAnnotationValueVisitor(), declaredTypes); annotationMembers.putAll(attribute, declaredTypes); } } return annotationMembers; } /** Returns an optional {@link TypeElement} for a class attribute on an annotation. */ public static Optional getOptionalAnnotationClassValue( Elements elements, AnnotationMirror annotation, String key) { return getAnnotationClassValues(elements, annotation).get(key).stream() .map(MoreTypes::asTypeElement) .collect(toOptional()); } /** Returns a list of {@link TypeElement}s for a class attribute on an annotation. */ public static ImmutableList getOptionalAnnotationClassValues( Elements elements, AnnotationMirror annotation, String key) { return ImmutableList.copyOf( getAnnotationClassValues(elements, annotation).get(key).stream() .map(MoreTypes::asTypeElement) .collect(Collectors.toList())); } private static final class DeclaredTypeAnnotationValueVisitor extends SimpleAnnotationValueVisitor7> { @Override public Void visitArray( List vals, Set types) { for (AnnotationValue val : vals) { val.accept(this, types); } return null; } @Override public Void visitType(TypeMirror t, Set types) { DeclaredType declared = MoreTypes.asDeclared(t); checkNotNull(declared); types.add(declared); return null; } } /** * If the received mirror represents a primitive type or an array of primitive types, this returns * the represented primitive type. Otherwise throws an IllegalStateException. */ public static PrimitiveType getPrimitiveType(TypeMirror type) { return type.accept( new SimpleTypeVisitor7 () { @Override public PrimitiveType visitArray(ArrayType type, Void unused) { return getPrimitiveType(type.getComponentType()); } @Override public PrimitiveType visitPrimitive(PrimitiveType type, Void unused) { return type; } @Override public PrimitiveType defaultAction(TypeMirror type, Void unused) { throw new IllegalStateException("Unhandled type: " + type); } }, null /* the Void accumulator */); } /** * Returns an {@link Optional#of} the declared type if the received mirror represents a declared * type or an array of declared types, otherwise returns {@link Optional#empty}. */ public static Optional getOptionalDeclaredType(TypeMirror type) { return Optional.ofNullable( type.accept( new SimpleTypeVisitor7(null /* defaultValue */) { @Override public DeclaredType visitArray(ArrayType type, Void unused) { return MoreTypes.asDeclared(type.getComponentType()); } @Override public DeclaredType visitDeclared(DeclaredType type, Void unused) { return type; } @Override public DeclaredType visitError(ErrorType type, Void unused) { return type; } }, null /* the Void accumulator */)); } /** * Returns the declared type if the received mirror represents a declared type or an array of * declared types, otherwise throws an {@link IllegalStateException}. */ public static DeclaredType getDeclaredType(TypeMirror type) { return getOptionalDeclaredType(type) .orElseThrow(() -> new IllegalStateException("Not a declared type: " + type)); } /** Gets the values from an annotation value representing a string array. */ public static ImmutableList getStringArrayAnnotationValue(AnnotationValue value) { return value.accept(new SimpleAnnotationValueVisitor7, Void>() { @Override public ImmutableList defaultAction(Object o, Void unused) { throw new IllegalStateException("Expected an array, got instead: " + o); } @Override public ImmutableList visitArray(List values, Void unused) { ImmutableList.Builder builder = ImmutableList.builder(); for (AnnotationValue value : values) { builder.add(getStringAnnotationValue(value)); } return builder.build(); } }, /* unused accumulator */ null); } /** Gets the values from an annotation value representing an int. */ public static Boolean getBooleanAnnotationValue(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor7() { @Override public Boolean defaultAction(Object o, Void unused) { throw new IllegalStateException("Expected a boolean, got instead: " + o); } @Override public Boolean visitBoolean(boolean value, Void unused) { return value; } }, /* unused accumulator */ null); } /** Gets the values from an annotation value representing an int. */ public static Integer getIntAnnotationValue(AnnotationValue value) { return value.accept(new SimpleAnnotationValueVisitor7() { @Override public Integer defaultAction(Object o, Void unused) { throw new IllegalStateException("Expected an int, got instead: " + o); } @Override public Integer visitInt(int value, Void unused) { return value; } }, /* unused accumulator */ null); } /** Gets the values from an annotation value representing a long. */ public static Long getLongAnnotationValue(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor7() { @Override public Long defaultAction(Object o, Void unused) { throw new IllegalStateException("Expected an int, got instead: " + o); } @Override public Long visitLong(long value, Void unused) { return value; } }, null /* unused accumulator */); } /** Gets the values from an annotation value representing a string. */ public static String getStringAnnotationValue(AnnotationValue value) { return value.accept(new SimpleAnnotationValueVisitor7() { @Override public String defaultAction(Object o, Void unused) { throw new IllegalStateException("Expected a string, got instead: " + o); } @Override public String visitString(String value, Void unused) { return value; } }, /* unused accumulator */ null); } /** Gets the values from an annotation value representing a DeclaredType. */ public static DeclaredType getDeclaredTypeAnnotationValue(AnnotationValue value) { return value.accept( new SimpleAnnotationValueVisitor7() { @Override public DeclaredType defaultAction(Object o, Void unused) { throw new IllegalStateException("Expected a TypeMirror, got instead: " + o); } @Override public DeclaredType visitType(TypeMirror typeMirror, Void unused) { return MoreTypes.asDeclared(typeMirror); } }, /* unused accumulator */ null); } private static final SimpleAnnotationValueVisitor7, Void> ENUM_ANNOTATION_VALUE_VISITOR = new SimpleAnnotationValueVisitor7, Void>() { @Override public ImmutableSet defaultAction(Object o, Void unused) { throw new IllegalStateException( "Expected an Enum or an Enum array, got instead: " + o); } @Override public ImmutableSet visitArray( List values, Void unused) { ImmutableSet.Builder builder = ImmutableSet.builder(); for (AnnotationValue value : values) { builder.addAll(value.accept(this, null)); } return builder.build(); } @Override public ImmutableSet visitEnumConstant( VariableElement value, Void unused) { return ImmutableSet.of(value); } }; /** Gets the values from an annotation value representing a Enum array. */ public static ImmutableSet getEnumArrayAnnotationValue(AnnotationValue value) { return value.accept(ENUM_ANNOTATION_VALUE_VISITOR, /* unused accumulator */ null); } /** Converts an annotation value map to be keyed by the attribute name. */ public static ImmutableMap convertToAttributeNameMap( Map annotationValues) { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Map.Entry e : annotationValues.entrySet()) { String attribute = e.getKey().getSimpleName().toString(); builder.put(attribute, e.getValue()); } return builder.build(); } /** Returns the given elements containing package element. */ public static PackageElement getPackageElement(Element originalElement) { checkNotNull(originalElement); for (Element e = originalElement; e != null; e = e.getEnclosingElement()) { if (e instanceof PackageElement) { return (PackageElement) e; } } throw new IllegalStateException("Cannot find a package for " + originalElement); } public static TypeElement getTopLevelType(Element originalElement) { checkNotNull(originalElement); for (Element e = originalElement; e != null; e = e.getEnclosingElement()) { if (isTopLevel(e)) { return MoreElements.asType(e); } } throw new IllegalStateException("Cannot find a top-level type for " + originalElement); } /** Returns true if the given element is a top-level element. */ public static boolean isTopLevel(Element element) { return element.getEnclosingElement().getKind() == ElementKind.PACKAGE; } /** Returns true if the given element is annotated with the given annotation. */ public static boolean hasAnnotation(Element element, Class annotation) { return element.getAnnotation(annotation) != null; } /** Returns true if the given element has an annotation with the given class name. */ public static boolean hasAnnotation(Element element, ClassName className) { return getAnnotationMirrorOptional(element, className).isPresent(); } /** Returns true if the given element has an annotation with the given class name. */ public static boolean hasAnnotation(AnnotationMirror mirror, ClassName className) { return hasAnnotation(mirror.getAnnotationType().asElement(), className); } /** Returns true if the given element is annotated with the given annotation. */ public static boolean hasAnnotation( AnnotationMirror mirror, Class annotation) { return hasAnnotation(mirror.getAnnotationType().asElement(), annotation); } /** Returns true if the given element has an annotation that is an error kind. */ public static boolean hasErrorTypeAnnotation(Element element) { for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { if (annotationMirror.getAnnotationType().getKind() == TypeKind.ERROR) { return true; } } return false; } /** * Returns all elements in the round that are annotated with at least 1 of the given * annotations. */ @SafeVarargs public static ImmutableSet getElementsAnnotatedWith(RoundEnvironment roundEnv, Class... annotations) { ImmutableSet.Builder builder = ImmutableSet.builder(); for (Class annotation : annotations){ builder.addAll(roundEnv.getElementsAnnotatedWith(annotation)); } return builder.build(); } /** * Returns the name of a class, including prefixing with enclosing class names. i.e. for inner * class Foo enclosed by Bar, returns Bar_Foo instead of just Foo */ public static String getEnclosedName(ClassName name) { return Joiner.on('_').join(name.simpleNames()); } /** Returns the name of a class. See {@link #getEnclosedName(ClassName)}. */ public static String getEnclosedName(TypeElement element) { return getEnclosedName(ClassName.get(element)); } /** * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with * {@code _}. */ public static ClassName getEnclosedClassName(ClassName className) { return ClassName.get(className.packageName(), getEnclosedName(className)); } /** * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with * {@code _}. */ public static ClassName getEnclosedClassName(TypeElement typeElement) { return getEnclosedClassName(ClassName.get(typeElement)); } /** Returns the fully qualified class name, with _ instead of . */ public static String getFullyQualifiedEnclosedClassName(ClassName className) { return className.packageName().replace('.', '_') + getEnclosedName(className); } /** * Returns the fully qualified class name, with _ instead of . For elements that are not type * elements, this continues to append the simple name of elements. For example, * foo_bar_Outer_Inner_fooMethod. */ public static String getFullEnclosedName(Element element) { Preconditions.checkNotNull(element); String qualifiedName = ""; while (element != null) { if (element.getKind().equals(ElementKind.PACKAGE)) { qualifiedName = asPackage(element).getQualifiedName() + qualifiedName; } else { // This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11 // contains newly added "module" enclosing elements of packages, which adds an addtional "_" // prefix to the name due to an empty module element compared with jdk8. if (!element.getSimpleName().toString().isEmpty()) { qualifiedName = "." + element.getSimpleName() + qualifiedName; } } element = element.getEnclosingElement(); } return qualifiedName.replace('.', '_'); } /** Appends the given string to the end of the class name. */ public static ClassName append(ClassName name, String suffix) { return name.peerClass(name.simpleName() + suffix); } /** Prepends the given string to the beginning of the class name. */ public static ClassName prepend(ClassName name, String prefix) { return name.peerClass(prefix + name.simpleName()); } /** * Removes the string {@code suffix} from the simple name of {@code type} and returns it. * * @throws BadInputException if the simple name of {@code type} does not end with {@code suffix} */ public static ClassName removeNameSuffix(TypeElement type, String suffix) { ClassName originalName = ClassName.get(type); String originalSimpleName = originalName.simpleName(); ProcessorErrors.checkState(originalSimpleName.endsWith(suffix), type, "Name of type %s must end with '%s'", originalName, suffix); String withoutSuffix = originalSimpleName.substring(0, originalSimpleName.length() - suffix.length()); return originalName.peerClass(withoutSuffix); } /** @see #getAnnotationMirror(Element, ClassName) */ public static AnnotationMirror getAnnotationMirror( Element element, Class annotationClass) { return getAnnotationMirror(element, ClassName.get(annotationClass)); } /** @see #getAnnotationMirror(Element, ClassName) */ public static AnnotationMirror getAnnotationMirror(Element element, String annotationClassName) { return getAnnotationMirror(element, ClassName.bestGuess(annotationClassName)); } /** * Returns the annotation mirror from the given element that corresponds to the given class. * * @throws IllegalStateException if the given element isn't annotated with that annotation. */ public static AnnotationMirror getAnnotationMirror(Element element, ClassName className) { Optional annotationMirror = getAnnotationMirrorOptional(element, className); if (annotationMirror.isPresent()) { return annotationMirror.get(); } else { throw new IllegalStateException( String.format( "Couldn't find annotation %s on element %s. Found annotations: %s", className, element.getSimpleName(), element.getAnnotationMirrors())); } } /** * Returns the annotation mirror from the given element that corresponds to the given class. * * @throws {@link IllegalArgumentException} if 2 or more annotations are found. * @return {@link Optional#empty()} if no annotation is found on the element. */ static Optional getAnnotationMirrorOptional( Element element, ClassName className) { return element.getAnnotationMirrors().stream() .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className)) .collect(toOptional()); } /** @return true if element inherits directly or indirectly from the className */ public static boolean isAssignableFrom(TypeElement element, ClassName className) { return isAssignableFromAnyOf(element, ImmutableSet.of(className)); } /** @return true if element inherits directly or indirectly from any of the classNames */ public static boolean isAssignableFromAnyOf(TypeElement element, ImmutableSet classNames) { for (ClassName className : classNames) { if (ClassName.get(element).equals(className)) { return true; } } TypeMirror superClass = element.getSuperclass(); // None type is returned if this is an interface or Object // Error type is returned for classes that are generated by this processor if ((superClass.getKind() != TypeKind.NONE) && (superClass.getKind() != TypeKind.ERROR)) { Preconditions.checkState(superClass.getKind() == TypeKind.DECLARED); if (isAssignableFromAnyOf(MoreTypes.asTypeElement(superClass), classNames)) { return true; } } for (TypeMirror iface : element.getInterfaces()) { // Skip errors and keep looking. This is especially needed for classes generated by this // processor. if (iface.getKind() == TypeKind.ERROR) { continue; } Preconditions.checkState(iface.getKind() == TypeKind.DECLARED, "Interface type is %s", iface.getKind()); if (isAssignableFromAnyOf(MoreTypes.asTypeElement(iface), classNames)) { return true; } } return false; } /** Returns methods from a given TypeElement, not including constructors. */ public static ImmutableList getMethods(TypeElement element) { ImmutableList.Builder builder = ImmutableList.builder(); for (Element e : element.getEnclosedElements()) { // Only look for executable elements, not fields, etc if (e instanceof ExecutableElement) { ExecutableElement method = (ExecutableElement) e; if (!method.getSimpleName().contentEquals(CONSTRUCTOR_NAME) && !method.getSimpleName().contentEquals(STATIC_INITIALIZER_NAME)) { builder.add(method); } } } return builder.build(); } public static ImmutableList getConstructors(TypeElement element) { ImmutableList.Builder builder = ImmutableList.builder(); for (Element enclosed : element.getEnclosedElements()) { // Only look for executable elements, not fields, etc if (enclosed instanceof ExecutableElement) { ExecutableElement method = (ExecutableElement) enclosed; if (method.getSimpleName().contentEquals(CONSTRUCTOR_NAME)) { builder.add(method); } } } return builder.build(); } /** * Returns all transitive methods from a given TypeElement, not including constructors. Also does * not include methods from Object or that override methods on Object. */ public static ImmutableList getAllMethods(TypeElement element) { ImmutableList.Builder builder = ImmutableList.builder(); builder.addAll( Iterables.filter( getMethods(element), method -> { return !isObjectMethod(method); })); TypeMirror superclass = element.getSuperclass(); if (superclass.getKind() != TypeKind.NONE) { TypeElement superclassElement = MoreTypes.asTypeElement(superclass); builder.addAll(getAllMethods(superclassElement)); } for (TypeMirror iface : element.getInterfaces()) { builder.addAll(getAllMethods(MoreTypes.asTypeElement(iface))); } return builder.build(); } /** Checks that the given element is not the error type. */ public static void checkForCompilationError(TypeElement e) { ProcessorErrors.checkState(e.asType().getKind() != TypeKind.ERROR, e, "Unable to resolve the type %s. Look for compilation errors above related to this type.", e); } private static void addInterfaceMethods( TypeElement type, ImmutableList.Builder interfaceMethods) { for (TypeMirror interfaceMirror : type.getInterfaces()) { TypeElement interfaceElement = MoreTypes.asTypeElement(interfaceMirror); interfaceMethods.addAll(getMethods(interfaceElement)); addInterfaceMethods(interfaceElement, interfaceMethods); } } /** * Finds methods of interfaces implemented by {@code type}. This method also checks the * superinterfaces of those interfaces. This method does not check the interfaces of any * superclass of {@code type}. */ public static ImmutableList methodsOnInterfaces(TypeElement type) { ImmutableList.Builder interfaceMethods = new ImmutableList.Builder<>(); addInterfaceMethods(type, interfaceMethods); return interfaceMethods.build(); } /** Returns MapKey annotated annotations found on an element. */ public static ImmutableList getMapKeyAnnotations(Element element) { return getAnnotationsAnnotatedWith(element, ClassName.get("dagger", "MapKey")); } /** Returns Qualifier annotated annotations found on an element. */ public static ImmutableList getQualifierAnnotations(Element element) { // TODO(bcorso): Consolidate this logic with InjectionAnnotations in Dagger ImmutableSet qualifiers = AnnotationMirrors.getAnnotatedAnnotations(element, Qualifier.class); KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil(); if (element.getKind() == ElementKind.FIELD // static fields are generally not supported, no need to get qualifier from kotlin metadata && !element.getModifiers().contains(STATIC) && metadataUtil.hasMetadata(element)) { VariableElement fieldElement = asVariable(element); return Stream.concat( qualifiers.stream(), metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement) ? Stream.empty() : metadataUtil .getSyntheticPropertyAnnotations(fieldElement, Qualifier.class) .stream()) .map(AnnotationMirrors.equivalence()::wrap) .distinct() .map(Wrapper::get) .collect(DaggerStreams.toImmutableList()); } else { return ImmutableList.copyOf(qualifiers); } } /** Returns Scope annotated annotations found on an element. */ public static ImmutableList getScopeAnnotations(Element element) { return getAnnotationsAnnotatedWith(element, ClassNames.SCOPE); } /** Returns annotations of element that are annotated with subAnnotation */ public static ImmutableList getAnnotationsAnnotatedWith( Element element, ClassName subAnnotation) { ImmutableList.Builder builder = ImmutableList.builder(); element.getAnnotationMirrors().stream() .filter(annotation -> hasAnnotation(annotation, subAnnotation)) .forEach(builder::add); return builder.build(); } /** Returns true if there are any annotations of element that are annotated with subAnnotation */ public static boolean hasAnnotationsAnnotatedWith(Element element, ClassName subAnnotation) { return !getAnnotationsAnnotatedWith(element, subAnnotation).isEmpty(); } /** * Returns true iff the given {@code method} is one of the public or protected methods on {@link * Object}, or an overridden version thereof. * *

This method ignores the return type of the given method, but this is generally fine since * two methods which only differ by their return type will cause a compiler error. (e.g. a * non-static method with the signature {@code int equals(Object)}) */ public static boolean isObjectMethod(ExecutableElement method) { // First check if this method is directly defined on Object Element enclosingElement = method.getEnclosingElement(); if (enclosingElement.getKind() == ElementKind.CLASS && TypeName.get(enclosingElement.asType()).equals(TypeName.OBJECT)) { return true; } if (method.getModifiers().contains(Modifier.STATIC)) { return false; } switch (method.getSimpleName().toString()) { case "equals": return (method.getParameters().size() == 1) && (method.getParameters().get(0).asType().toString().equals("java.lang.Object")); case "hashCode": case "toString": case "clone": case "getClass": case "notify": case "notifyAll": case "finalize": return method.getParameters().isEmpty(); case "wait": if (method.getParameters().isEmpty()) { return true; } else if ((method.getParameters().size() == 1) && (method.getParameters().get(0).asType().toString().equals("long"))) { return true; } else if ((method.getParameters().size() == 2) && (method.getParameters().get(0).asType().toString().equals("long")) && (method.getParameters().get(1).asType().toString().equals("int"))) { return true; } return false; default: return false; } } public static ParameterSpec parameterSpecFromVariableElement(VariableElement element) { return ParameterSpec.builder( ClassName.get(element.asType()), upperToLowerCamel(element.getSimpleName().toString())) .build(); } /** * Shortcut for converting from upper camel to lower camel case * *

Example: "SomeString" => "someString" */ public static String upperToLowerCamel(String upperCamel) { return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel); } /** @return copy of the given MethodSpec as {@link MethodSpec.Builder} with method body removed */ public static MethodSpec.Builder copyMethodSpecWithoutBody(MethodSpec methodSpec) { MethodSpec.Builder builder; if (methodSpec.isConstructor()) { // Constructors cannot have return types builder = MethodSpec.constructorBuilder(); } else { builder = MethodSpec.methodBuilder(methodSpec.name) .returns(methodSpec.returnType); } return builder .addAnnotations(methodSpec.annotations) .addModifiers(methodSpec.modifiers) .addParameters(methodSpec.parameters) .addExceptions(methodSpec.exceptions) .addJavadoc(methodSpec.javadoc.toString()) .addTypeVariables(methodSpec.typeVariables); } /** @return A method spec for an empty constructor (useful for abstract Dagger modules). */ public static MethodSpec privateEmptyConstructor() { return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); } /** * Returns true if the given method is annotated with one of the annotations Dagger recognizes * for abstract methods (e.g. @Binds). */ public static boolean hasDaggerAbstractMethodAnnotation(ExecutableElement method) { return hasAnnotation(method, ClassNames.BINDS) || hasAnnotation(method, ClassNames.BINDS_OPTIONAL_OF) || hasAnnotation(method, ClassNames.MULTIBINDS) || hasAnnotation(method, ClassNames.CONTRIBUTES_ANDROID_INJECTOR); } public static ImmutableSet toTypeElements(Elements elements, String[] classes) { return FluentIterable.from(classes).transform(elements::getTypeElement).toSet(); } public static ImmutableSet toClassNames(Iterable elements) { return FluentIterable.from(elements).transform(ClassName::get).toSet(); } public static boolean requiresModuleInstance(Elements elements, TypeElement module) { // Binding methods that lack ABSTRACT or STATIC require module instantiation. // Required by Dagger. See b/31489617. return ElementFilter.methodsIn(elements.getAllMembers(module)).stream() .filter(Processors::isBindingMethod) .map(ExecutableElement::getModifiers) .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC)) // TODO(erichang): Getting a new KotlinMetadataUtil each time isn't great here, but until // we have some sort of dependency management it will be difficult to share the instance. && !KotlinMetadataUtils.getMetadataUtil().isObjectOrCompanionObjectClass(module); } private static boolean isBindingMethod(ExecutableElement method) { return hasAnnotation(method, ClassNames.PROVIDES) || hasAnnotation(method, ClassNames.BINDS) || hasAnnotation(method, ClassNames.BINDS_OPTIONAL_OF) || hasAnnotation(method, ClassNames.MULTIBINDS); } public static void addGeneratedAnnotation( TypeSpec.Builder typeSpecBuilder, ProcessingEnvironment env, Class generatorClass) { addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName()); } public static void addGeneratedAnnotation( TypeSpec.Builder typeSpecBuilder, ProcessingEnvironment env, String generatorClass) { GeneratedAnnotations.generatedAnnotation(env.getElementUtils(), env.getSourceVersion()) .ifPresent( annotation -> typeSpecBuilder.addAnnotation( AnnotationSpec.builder(ClassName.get(annotation)) .addMember("value", "$S", generatorClass) .build())); } public static AnnotationSpec getOriginatingElementAnnotation(TypeElement element) { TypeName rawType = rawTypeName(ClassName.get(getTopLevelType(element))); return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT) .addMember("topLevelClass", "$T.class", rawType) .build(); } /** * Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a * parameterized type, it returns the argument unchanged. */ public static TypeName rawTypeName(TypeName typeName) { return (typeName instanceof ParameterizedTypeName) ? ((ParameterizedTypeName) typeName).rawType : typeName; } public static Optional getOriginatingTestElement( Element element, Elements elements) { TypeElement topLevelType = getOriginatingTopLevelType(element, elements); return hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST) ? Optional.of(asType(topLevelType)) : Optional.empty(); } private static TypeElement getOriginatingTopLevelType(Element element, Elements elements) { TypeElement topLevelType = getTopLevelType(element); if (hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) { return getOriginatingTopLevelType( getAnnotationClassValue( elements, getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT), "topLevelClass"), elements); } return topLevelType; } private Processors() {} }