blob: fb6a12b7414ac86935a197c4188f7cf43c613ee7 [file] [log] [blame]
/*
* 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;
import static com.google.auto.common.MoreElements.asExecutable;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreElements.hasModifiers;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.asList;
import static dagger.internal.codegen.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.Formatter.formatArgumentInList;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static javax.lang.model.element.Modifier.ABSTRACT;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.graph.Traverser;
import com.squareup.javapoet.ClassName;
import dagger.Reusable;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor8;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleElementVisitor8;
import javax.lang.model.util.Types;
/** Extension of {@link Elements} that adds Dagger-specific methods. */
@Reusable
final class DaggerElements implements Elements {
private final Elements elements;
private final Types types;
@VisibleForTesting
DaggerElements(Elements elements, Types types) {
this.elements = checkNotNull(elements);
this.types = checkNotNull(types);
}
DaggerElements(ProcessingEnvironment processingEnv) {
this(processingEnv.getElementUtils(), processingEnv.getTypeUtils());
}
/**
* Returns {@code true} if {@code encloser} is equal to {@code enclosed} or recursively encloses
* it.
*/
static boolean elementEncloses(TypeElement encloser, Element enclosed) {
return Iterables.contains(GET_ENCLOSED_ELEMENTS.breadthFirst(encloser), enclosed);
}
private static final Traverser<Element> GET_ENCLOSED_ELEMENTS =
Traverser.forTree(Element::getEnclosedElements);
ImmutableSet<ExecutableElement> getUnimplementedMethods(TypeElement type) {
return FluentIterable.from(getLocalAndInheritedMethods(type, types, elements))
.filter(hasModifiers(ABSTRACT))
.toSet();
}
/** Returns the type element for a class. */
TypeElement getTypeElement(Class<?> clazz) {
return getTypeElement(clazz.getCanonicalName());
}
@Override
public TypeElement getTypeElement(CharSequence name) {
return elements.getTypeElement(name);
}
/** Returns the type element for a class name. */
TypeElement getTypeElement(ClassName className) {
return getTypeElement(className.withoutAnnotations().toString());
}
/**
* Returns a useful string form for an element.
*
* <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name.
*
* <p>Parameters are given with their enclosing executable, with other parameters elided.
*/
static String elementToString(Element element) {
return element.accept(ELEMENT_TO_STRING, null);
}
private static final ElementVisitor<String, Void> ELEMENT_TO_STRING =
new ElementKindVisitor8<String, Void>() {
@Override
public String visitExecutable(ExecutableElement executableElement, Void aVoid) {
return enclosingTypeAndMemberName(executableElement)
.append(
executableElement.getParameters().stream()
.map(parameter -> parameter.asType().toString())
.collect(joining(", ", "(", ")")))
.toString();
}
@Override
public String visitVariableAsParameter(VariableElement parameter, Void aVoid) {
ExecutableElement methodOrConstructor = asExecutable(parameter.getEnclosingElement());
return enclosingTypeAndMemberName(methodOrConstructor)
.append('(')
.append(
formatArgumentInList(
methodOrConstructor.getParameters().indexOf(parameter),
methodOrConstructor.getParameters().size(),
parameter.getSimpleName()))
.append(')')
.toString();
}
@Override
public String visitVariableAsField(VariableElement field, Void aVoid) {
return enclosingTypeAndMemberName(field).toString();
}
@Override
public String visitType(TypeElement type, Void aVoid) {
return type.getQualifiedName().toString();
}
@Override
protected String defaultAction(Element element, Void aVoid) {
throw new UnsupportedOperationException(
"Can't determine string for " + element.getKind() + " element " + element);
}
private StringBuilder enclosingTypeAndMemberName(Element element) {
StringBuilder name = new StringBuilder(element.getEnclosingElement().accept(this, null));
if (!element.getSimpleName().contentEquals("<init>")) {
name.append('.').append(element.getSimpleName());
}
return name;
}
};
/** Returns the argument or the closest enclosing element that is a {@link TypeElement}. */
static TypeElement closestEnclosingTypeElement(Element element) {
return element.accept(CLOSEST_ENCLOSING_TYPE_ELEMENT, null);
}
private static final ElementVisitor<TypeElement, Void> CLOSEST_ENCLOSING_TYPE_ELEMENT =
new SimpleElementVisitor8<TypeElement, Void>() {
@Override
protected TypeElement defaultAction(Element element, Void p) {
return element.getEnclosingElement().accept(this, null);
}
@Override
public TypeElement visitType(TypeElement type, Void p) {
return type;
}
};
/**
* Compares elements according to their declaration order among siblings. Only valid to compare
* elements enclosed by the same parent.
*/
static final Comparator<Element> DECLARATION_ORDER =
comparing(element -> element.getEnclosingElement().getEnclosedElements().indexOf(element));
/**
* 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}.
*/
static boolean isAnyAnnotationPresent(
Element element, Iterable<? extends Class<? extends Annotation>> annotationClasses) {
for (Class<? extends Annotation> annotation : annotationClasses) {
if (MoreElements.isAnnotationPresent(element, annotation)) {
return true;
}
}
return false;
}
@SafeVarargs
static boolean isAnyAnnotationPresent(
Element element,
Class<? extends Annotation> first,
Class<? extends Annotation>... otherAnnotations) {
return isAnyAnnotationPresent(element, asList(first, otherAnnotations));
}
/**
* Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain
* AnnotationMirror#getAnnotationType() annotation type} is equivalent to {@code annotationType}.
*/
static boolean isAnnotationPresent(Element element, TypeMirror annotationType) {
return element
.getAnnotationMirrors()
.stream()
.map(AnnotationMirror::getAnnotationType)
.anyMatch(candidate -> MoreTypes.equivalence().equivalent(candidate, annotationType));
}
/**
* Returns the annotation present on {@code element} whose type is {@code first} or within {@code
* rest}, checking each annotation type in order.
*/
@SafeVarargs
static Optional<AnnotationMirror> getAnyAnnotation(
Element element, Class<? extends Annotation> first, Class<? extends Annotation>... rest) {
return getAnyAnnotation(element, asList(first, rest));
}
/**
* Returns the annotation present on {@code element} whose type is in {@code annotations},
* checking each annotation type in order.
*/
static Optional<AnnotationMirror> getAnyAnnotation(
Element element, Collection<? extends Class<? extends Annotation>> annotations) {
return element
.getAnnotationMirrors()
.stream()
.filter(hasAnnotationTypeIn(annotations))
.map((AnnotationMirror a) -> a) // Avoid returning Optional<? extends AnnotationMirror>.
.findFirst();
}
/** Returns the annotations present on {@code element} of all types. */
@SafeVarargs
static ImmutableSet<AnnotationMirror> getAllAnnotations(
Element element, Class<? extends Annotation> first, Class<? extends Annotation>... rest) {
return element
.getAnnotationMirrors()
.stream()
.filter(hasAnnotationTypeIn(asList(first, rest)))
.collect(toImmutableSet());
}
/**
* 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.
*/
static Optional<AnnotationMirror> getAnnotationMirror(
Element element, Class<? extends Annotation> annotationClass) {
return Optional.ofNullable(MoreElements.getAnnotationMirror(element, annotationClass).orNull());
}
private static Predicate<AnnotationMirror> hasAnnotationTypeIn(
Collection<? extends Class<? extends Annotation>> annotations) {
Set<String> annotationClassNames =
annotations.stream().map(Class::getCanonicalName).collect(toSet());
return annotation ->
annotationClassNames.contains(
MoreTypes.asTypeElement(annotation.getAnnotationType()).getQualifiedName().toString());
}
static ImmutableSet<String> suppressedWarnings(Element element) {
SuppressWarnings suppressedWarnings = element.getAnnotation(SuppressWarnings.class);
if (suppressedWarnings == null) {
return ImmutableSet.of();
}
return ImmutableSet.copyOf(suppressedWarnings.value());
}
/**
* Invokes {@link Elements#getTypeElement(CharSequence)}, throwing {@link TypeNotPresentException}
* if it is not accessible in the current compilation.
*/
TypeElement checkTypePresent(String typeName) {
TypeElement type = elements.getTypeElement(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);
}
@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) {
return elements.getName(cs);
}
@Override
public boolean isFunctionalInterface(TypeElement type) {
return elements.isFunctionalInterface(type);
}
}