| /* |
| * Copyright (C) 2013 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.internal.codegen.langmodel; |
| |
| import static androidx.room.compiler.processing.compat.XConverters.toJavac; |
| import static com.google.auto.common.MoreElements.asExecutable; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.collect.Lists.asList; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; |
| import static java.util.Comparator.comparing; |
| |
| import androidx.room.compiler.processing.XMethodElement; |
| import androidx.room.compiler.processing.XProcessingEnv; |
| import androidx.room.compiler.processing.XTypeElement; |
| import com.google.auto.common.MoreElements; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.squareup.javapoet.ClassName; |
| import dagger.Reusable; |
| import dagger.internal.codegen.base.ClearableCache; |
| import java.io.Writer; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.stream.Collectors; |
| 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.Name; |
| import javax.lang.model.element.PackageElement; |
| import javax.lang.model.element.QualifiedNameable; |
| 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.ExecutableType; |
| import javax.lang.model.type.IntersectionType; |
| import javax.lang.model.type.NoType; |
| import javax.lang.model.type.PrimitiveType; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.type.TypeVariable; |
| import javax.lang.model.type.WildcardType; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.SimpleTypeVisitor8; |
| import javax.lang.model.util.Types; |
| |
| /** Extension of {@link Elements} that adds Dagger-specific methods. */ |
| @Reusable |
| public final class DaggerElements implements Elements, ClearableCache { |
| private final Map<TypeElement, ImmutableSet<ExecutableElement>> getLocalAndInheritedMethodsCache = |
| new HashMap<>(); |
| private final Elements elements; |
| private final Types types; |
| |
| public DaggerElements(Elements elements, Types types) { |
| this.elements = checkNotNull(elements); |
| this.types = checkNotNull(types); |
| } |
| |
| /** |
| * Returns {@code true} if {@code encloser} is equal to or recursively encloses {@code enclosed}. |
| */ |
| public static boolean transitivelyEncloses(Element encloser, Element enclosed) { |
| Element current = enclosed; |
| while (current != null) { |
| if (current.equals(encloser)) { |
| return true; |
| } |
| current = current.getEnclosingElement(); |
| } |
| return false; |
| } |
| |
| public ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(TypeElement type) { |
| return getLocalAndInheritedMethodsCache.computeIfAbsent( |
| type, k -> MoreElements.getLocalAndInheritedMethods(type, types, elements)); |
| } |
| |
| @Override |
| public TypeElement getTypeElement(CharSequence name) { |
| return elements.getTypeElement(name); |
| } |
| |
| /** Returns the type element for a class name. */ |
| public TypeElement getTypeElement(ClassName className) { |
| return getTypeElement(className.canonicalName()); |
| } |
| |
| /** Returns the argument or the closest enclosing element that is a {@link TypeElement}. */ |
| public static TypeElement closestEnclosingTypeElement(Element element) { |
| Element current = element; |
| while (current != null) { |
| if (MoreElements.isType(current)) { |
| return MoreElements.asType(current); |
| } |
| current = current.getEnclosingElement(); |
| } |
| throw new IllegalStateException("There is no enclosing TypeElement for: " + element); |
| } |
| |
| /** |
| * Compares elements according to their declaration order among siblings. Only valid to compare |
| * elements enclosed by the same parent. |
| */ |
| public static final Comparator<Element> DECLARATION_ORDER = |
| comparing(element -> siblings(element).indexOf(element)); |
| |
| // For parameter elements, element.getEnclosingElement().getEnclosedElements() is empty. So |
| // instead look at the parameter list of the enclosing executable. |
| private static List<? extends Element> siblings(Element element) { |
| return element.getKind().equals(ElementKind.PARAMETER) |
| ? asExecutable(element.getEnclosingElement()).getParameters() |
| : element.getEnclosingElement().getEnclosedElements(); |
| } |
| |
| /** |
| * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain |
| * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as any of |
| * that of {@code annotationClasses}. |
| */ |
| public static boolean isAnyAnnotationPresent( |
| Element element, Iterable<ClassName> annotationClasses) { |
| for (ClassName annotation : annotationClasses) { |
| if (isAnnotationPresent(element, annotation)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @SafeVarargs |
| public static boolean isAnyAnnotationPresent( |
| Element element, ClassName first, ClassName... otherAnnotations) { |
| return isAnyAnnotationPresent(element, asList(first, otherAnnotations)); |
| } |
| |
| /** |
| * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@link |
| * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of |
| * {@code annotationClass}. This method is a safer alternative to calling {@link |
| * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with |
| * annotation proxies. |
| */ |
| public static boolean isAnnotationPresent(Element element, ClassName annotationName) { |
| return getAnnotationMirror(element, annotationName).isPresent(); |
| } |
| |
| // Note: This is similar to auto-common's MoreElements except using ClassName rather than Class. |
| // TODO(bcorso): Contribute a String version to auto-common's MoreElements? |
| /** |
| * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on |
| * {@code element}, or {@link Optional#empty()} if no such annotation exists. This method is a |
| * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with |
| * annotation proxies. |
| */ |
| public static Optional<AnnotationMirror> getAnnotationMirror( |
| Element element, ClassName annotationName) { |
| String annotationClassName = annotationName.canonicalName(); |
| for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { |
| TypeElement annotationTypeElement = |
| MoreElements.asType(annotationMirror.getAnnotationType().asElement()); |
| if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { |
| return Optional.of(annotationMirror); |
| } |
| } |
| return Optional.empty(); |
| } |
| |
| public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations( |
| Element element, ClassName annotationName) { |
| return element.getAnnotationMirrors().stream() |
| .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) |
| .collect(toImmutableSet()); |
| } |
| |
| /** |
| * Returns the field descriptor of the given {@code element}. |
| * |
| * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST. |
| * |
| * <p>For reference, see the <a |
| * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2">JVM |
| * specification, section 4.3.2</a>. |
| */ |
| public static String getFieldDescriptor(VariableElement element) { |
| return element.getSimpleName() + ":" + getDescriptor(element.asType()); |
| } |
| |
| /** |
| * Returns the method descriptor of the given {@code element}. |
| * |
| * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST. |
| * |
| * <p>For reference, see the <a |
| * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3">JVM |
| * specification, section 4.3.3</a>. |
| */ |
| // TODO(bcorso): Expose getMethodDescriptor() method in XProcessing instead. |
| public static String getMethodDescriptor(XMethodElement element) { |
| return getMethodDescriptor(toJavac(element)); |
| } |
| |
| /** |
| * Returns the method descriptor of the given {@code element}. |
| * |
| * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST. |
| * |
| * <p>For reference, see the <a |
| * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3">JVM |
| * specification, section 4.3.3</a>. |
| */ |
| public static String getMethodDescriptor(ExecutableElement element) { |
| return element.getSimpleName() + getDescriptor(element.asType()); |
| } |
| |
| private static String getDescriptor(TypeMirror t) { |
| return t.accept(JVM_DESCRIPTOR_TYPE_VISITOR, null); |
| } |
| |
| private static final SimpleTypeVisitor8<String, Void> JVM_DESCRIPTOR_TYPE_VISITOR = |
| new SimpleTypeVisitor8<String, Void>() { |
| |
| @Override |
| public String visitArray(ArrayType arrayType, Void v) { |
| return "[" + getDescriptor(arrayType.getComponentType()); |
| } |
| |
| @Override |
| public String visitDeclared(DeclaredType declaredType, Void v) { |
| return "L" + getInternalName(declaredType.asElement()) + ";"; |
| } |
| |
| @Override |
| public String visitError(ErrorType errorType, Void v) { |
| // For descriptor generating purposes we don't need a fully modeled type since we are |
| // only interested in obtaining the class name in its "internal form". |
| return visitDeclared(errorType, v); |
| } |
| |
| @Override |
| public String visitExecutable(ExecutableType executableType, Void v) { |
| String parameterDescriptors = |
| executableType.getParameterTypes().stream() |
| .map(DaggerElements::getDescriptor) |
| .collect(Collectors.joining()); |
| String returnDescriptor = getDescriptor(executableType.getReturnType()); |
| return "(" + parameterDescriptors + ")" + returnDescriptor; |
| } |
| |
| @Override |
| public String visitIntersection(IntersectionType intersectionType, Void v) { |
| // For a type variable with multiple bounds: "the erasure of a type variable is determined |
| // by the first type in its bound" - JVM Spec Sec 4.4 |
| return getDescriptor(intersectionType.getBounds().get(0)); |
| } |
| |
| @Override |
| public String visitNoType(NoType noType, Void v) { |
| return "V"; |
| } |
| |
| @Override |
| public String visitPrimitive(PrimitiveType primitiveType, Void v) { |
| switch (primitiveType.getKind()) { |
| case BOOLEAN: |
| return "Z"; |
| case BYTE: |
| return "B"; |
| case SHORT: |
| return "S"; |
| case INT: |
| return "I"; |
| case LONG: |
| return "J"; |
| case CHAR: |
| return "C"; |
| case FLOAT: |
| return "F"; |
| case DOUBLE: |
| return "D"; |
| default: |
| throw new IllegalArgumentException("Unknown primitive type."); |
| } |
| } |
| |
| @Override |
| public String visitTypeVariable(TypeVariable typeVariable, Void v) { |
| // The erasure of a type variable is the erasure of its leftmost bound. - JVM Spec Sec 4.6 |
| return getDescriptor(typeVariable.getUpperBound()); |
| } |
| |
| @Override |
| public String defaultAction(TypeMirror typeMirror, Void v) { |
| throw new IllegalArgumentException("Unsupported type: " + typeMirror); |
| } |
| |
| @Override |
| public String visitWildcard(WildcardType wildcardType, Void v) { |
| return ""; |
| } |
| |
| /** |
| * Returns the name of this element in its "internal form". |
| * |
| * <p>For reference, see the <a |
| * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.2">JVM |
| * specification, section 4.2</a>. |
| */ |
| private String getInternalName(Element element) { |
| try { |
| TypeElement typeElement = MoreElements.asType(element); |
| switch (typeElement.getNestingKind()) { |
| case TOP_LEVEL: |
| return typeElement.getQualifiedName().toString().replace('.', '/'); |
| case MEMBER: |
| return getInternalName(typeElement.getEnclosingElement()) |
| + "$" |
| + typeElement.getSimpleName(); |
| default: |
| throw new IllegalArgumentException("Unsupported nesting kind."); |
| } |
| } catch (IllegalArgumentException e) { |
| // Not a TypeElement, try something else... |
| } |
| |
| if (element instanceof QualifiedNameable) { |
| QualifiedNameable qualifiedNameElement = (QualifiedNameable) element; |
| return qualifiedNameElement.getQualifiedName().toString().replace('.', '/'); |
| } |
| |
| return element.getSimpleName().toString(); |
| } |
| }; |
| |
| /** Returns the type element or throws {@link TypeNotPresentException} if it is null. */ |
| public static XTypeElement checkTypePresent(XProcessingEnv processingEnv, ClassName className) { |
| return checkTypePresent(processingEnv, className.canonicalName()); |
| } |
| |
| /** Returns the type element or throws {@link TypeNotPresentException} if it is null. */ |
| public static XTypeElement checkTypePresent(XProcessingEnv processingEnv, String typeName) { |
| XTypeElement type = processingEnv.findTypeElement(typeName); |
| if (type == null) { |
| throw new TypeNotPresentException(typeName, null); |
| } |
| return type; |
| } |
| |
| @Override |
| public PackageElement getPackageElement(CharSequence name) { |
| return elements.getPackageElement(name); |
| } |
| |
| @Override |
| public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults( |
| AnnotationMirror a) { |
| return elements.getElementValuesWithDefaults(a); |
| } |
| |
| /** Returns a map of annotation values keyed by attribute name. */ |
| public Map<String, ? extends AnnotationValue> getElementValuesWithDefaultsByName( |
| AnnotationMirror a) { |
| ImmutableMap.Builder<String, AnnotationValue> builder = ImmutableMap.builder(); |
| getElementValuesWithDefaults(a).forEach((k, v) -> builder.put(k.getSimpleName().toString(), v)); |
| return builder.build(); |
| } |
| |
| @Override |
| public String getDocComment(Element e) { |
| return elements.getDocComment(e); |
| } |
| |
| @Override |
| public boolean isDeprecated(Element e) { |
| return elements.isDeprecated(e); |
| } |
| |
| @Override |
| public Name getBinaryName(TypeElement type) { |
| return elements.getBinaryName(type); |
| } |
| |
| @Override |
| public PackageElement getPackageOf(Element type) { |
| return elements.getPackageOf(type); |
| } |
| |
| @Override |
| public List<? extends Element> getAllMembers(TypeElement type) { |
| return elements.getAllMembers(type); |
| } |
| |
| @Override |
| public List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e) { |
| return elements.getAllAnnotationMirrors(e); |
| } |
| |
| @Override |
| public boolean hides(Element hider, Element hidden) { |
| return elements.hides(hider, hidden); |
| } |
| |
| @Override |
| public boolean overrides( |
| ExecutableElement overrider, ExecutableElement overridden, TypeElement type) { |
| return elements.overrides(overrider, overridden, type); |
| } |
| |
| @Override |
| public String getConstantExpression(Object value) { |
| return elements.getConstantExpression(value); |
| } |
| |
| @Override |
| public void printElements(Writer w, Element... elements) { |
| this.elements.printElements(w, elements); |
| } |
| |
| @Override |
| public Name getName(CharSequence cs) { // SUPPRESS_GET_NAME_CHECK: This is not xprocessing usage. |
| return elements.getName(cs); // SUPPRESS_GET_NAME_CHECK: This is not xprocessing usage. |
| } |
| |
| @Override |
| public boolean isFunctionalInterface(TypeElement type) { |
| return elements.isFunctionalInterface(type); |
| } |
| |
| @Override |
| public void clearCache() { |
| getLocalAndInheritedMethodsCache.clear(); |
| } |
| } |