aboutsummaryrefslogtreecommitdiff
path: root/common/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/main/java')
-rw-r--r--common/src/main/java/com/google/auto/common/AnnotationMirrors.java11
-rw-r--r--common/src/main/java/com/google/auto/common/AnnotationOutput.java266
-rw-r--r--common/src/main/java/com/google/auto/common/AnnotationValues.java17
-rw-r--r--common/src/main/java/com/google/auto/common/Overrides.java11
-rw-r--r--common/src/main/java/com/google/auto/common/Visibility.java6
5 files changed, 306 insertions, 5 deletions
diff --git a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
index 9ce5cd9b..2f59dd39 100644
--- a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
+++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
@@ -191,5 +191,16 @@ public final class AnnotationMirrors {
.collect(toImmutableSet());
}
+ /**
+ * Returns a string representation of the given annotation mirror, suitable for inclusion in a
+ * Java source file to reproduce the annotation in source form.
+ *
+ * <p>Fully qualified names are used for types in annotations, class literals, and enum constants,
+ * ensuring that the source form will compile without requiring additional imports.
+ */
+ public static String toString(AnnotationMirror annotationMirror) {
+ return AnnotationOutput.toString(annotationMirror);
+ }
+
private AnnotationMirrors() {}
}
diff --git a/common/src/main/java/com/google/auto/common/AnnotationOutput.java b/common/src/main/java/com/google/auto/common/AnnotationOutput.java
new file mode 100644
index 00000000..e52bde66
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/AnnotationOutput.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2014 Google LLC
+ *
+ * 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 com.google.auto.common;
+
+import static com.google.auto.common.MoreTypes.asTypeElement;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Handling of default values for annotation members.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+final class AnnotationOutput {
+ private AnnotationOutput() {} // There are no instances of this class.
+
+ /**
+ * Visitor that produces a string representation of an annotation value, suitable for inclusion in
+ * a Java source file as an annotation member or as the initializer of a variable of the
+ * appropriate type. The syntax for the two is the same except for annotation members that are
+ * themselves annotations. Within an annotation, an annotation member can be written as
+ * {@code @NestedAnnotation(...)}, while in an initializer it must be written as an object, for
+ * example the construction of an {@code @AutoAnnotation} class. That's why we have this abstract
+ * class and two concrete subclasses.
+ */
+ private static class SourceFormVisitor
+ extends SimpleAnnotationValueVisitor8<@Nullable Void, StringBuilder> {
+
+ private String formatType(TypeMirror typeMirror) {
+ return asTypeElement(typeMirror).getQualifiedName().toString();
+ }
+
+ @Override
+ protected @Nullable Void defaultAction(Object value, StringBuilder sb) {
+ sb.append(value);
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitArray(List<? extends AnnotationValue> values, StringBuilder sb) {
+ sb.append('{');
+ String sep = "";
+ for (AnnotationValue value : values) {
+ sb.append(sep);
+ visit(value, sb);
+ sep = ", ";
+ }
+ sb.append('}');
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitByte(byte b, StringBuilder sb) {
+ sb.append("(byte) ").append(b);
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitShort(short s, StringBuilder sb) {
+ sb.append("(short) ").append(s);
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitChar(char c, StringBuilder sb) {
+ appendQuoted(sb, c);
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitLong(long i, StringBuilder sb) {
+ sb.append(i).append('L');
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitDouble(double d, StringBuilder sb) {
+ if (Double.isNaN(d)) {
+ sb.append("Double.NaN");
+ } else if (d == Double.POSITIVE_INFINITY) {
+ sb.append("Double.POSITIVE_INFINITY");
+ } else if (d == Double.NEGATIVE_INFINITY) {
+ sb.append("Double.NEGATIVE_INFINITY");
+ } else {
+ sb.append(d);
+ }
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitFloat(float f, StringBuilder sb) {
+ if (Float.isNaN(f)) {
+ sb.append("Float.NaN");
+ } else if (f == Float.POSITIVE_INFINITY) {
+ sb.append("Float.POSITIVE_INFINITY");
+ } else if (f == Float.NEGATIVE_INFINITY) {
+ sb.append("Float.NEGATIVE_INFINITY");
+ } else {
+ sb.append(f).append('F');
+ }
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitEnumConstant(VariableElement c, StringBuilder sb) {
+ sb.append(formatType(c.asType())).append('.').append(c.getSimpleName());
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitString(String s, StringBuilder sb) {
+ appendQuoted(sb, s);
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitType(TypeMirror classConstant, StringBuilder sb) {
+ sb.append(formatType(classConstant)).append(".class");
+ return null;
+ }
+
+ @Override
+ public @Nullable Void visitAnnotation(AnnotationMirror a, StringBuilder sb) {
+ sb.append('@').append(formatType(a.getAnnotationType()));
+ ImmutableMap<ExecutableElement, AnnotationValue> map =
+ ImmutableMap.copyOf(a.getElementValues());
+ if (!map.isEmpty()) {
+ sb.append('(');
+ Optional<AnnotationValue> shortForm = shortForm(map);
+ if (shortForm.isPresent()) {
+ this.visit(maybeShorten(shortForm.get()), sb);
+ } else {
+ String sep = "";
+ for (Map.Entry<ExecutableElement, AnnotationValue> entry : map.entrySet()) {
+ sb.append(sep).append(entry.getKey().getSimpleName()).append(" = ");
+ sep = ", ";
+ this.visit(maybeShorten(entry.getValue()), sb);
+ }
+ }
+ sb.append(')');
+ }
+ return null;
+ }
+ }
+
+ private static AnnotationValue maybeShorten(AnnotationValue value) {
+ return ARRAY_VISITOR.visit(value, value);
+ }
+
+ private static final AnnotationValueVisitor<AnnotationValue, AnnotationValue> ARRAY_VISITOR =
+ new SimpleAnnotationValueVisitor8<AnnotationValue, AnnotationValue>() {
+ @Override
+ public AnnotationValue visitArray(
+ List<? extends AnnotationValue> values, AnnotationValue input) {
+ if (values.size() == 1) {
+ // We can shorten @Foo(a = {23}) to @Foo(a = 23). For the specific case where `a` is
+ // actually `value`, we'll already have shortened that in visitAnnotation, so
+ // effectively we go from @Foo(value = {23}) to @Foo({23}) to @Foo(23).
+ return Iterables.getOnlyElement(values);
+ }
+ return input;
+ }
+
+ @Override
+ protected AnnotationValue defaultAction(Object o, AnnotationValue input) {
+ return input;
+ }
+ };
+
+ // We can shorten @Annot(value = 23) to @Annot(23).
+ private static Optional<AnnotationValue> shortForm(
+ Map<ExecutableElement, AnnotationValue> values) {
+ if (values.size() == 1
+ && Iterables.getOnlyElement(values.keySet()).getSimpleName().contentEquals("value")) {
+ return Optional.of(Iterables.getOnlyElement(values.values()));
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Returns a string representation of the given annotation value, suitable for inclusion in a Java
+ * source file as the initializer of a variable of the appropriate type.
+ */
+ static String toString(AnnotationValue annotationValue) {
+ StringBuilder sb = new StringBuilder();
+ new SourceFormVisitor().visit(annotationValue, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Returns a string representation of the given annotation mirror, suitable for inclusion in a
+ * Java source file to reproduce the annotation in source form.
+ */
+ static String toString(AnnotationMirror annotationMirror) {
+ StringBuilder sb = new StringBuilder();
+ new SourceFormVisitor().visitAnnotation(annotationMirror, sb);
+ return sb.toString();
+ }
+
+ private static StringBuilder appendQuoted(StringBuilder sb, String s) {
+ sb.append('"');
+ for (int i = 0; i < s.length(); i++) {
+ appendEscaped(sb, s.charAt(i));
+ }
+ return sb.append('"');
+ }
+
+ private static StringBuilder appendQuoted(StringBuilder sb, char c) {
+ sb.append('\'');
+ appendEscaped(sb, c);
+ return sb.append('\'');
+ }
+
+ private static void appendEscaped(StringBuilder sb, char c) {
+ switch (c) {
+ case '\\':
+ case '"':
+ case '\'':
+ sb.append('\\').append(c);
+ break;
+ case '\n':
+ sb.append("\\n");
+ break;
+ case '\r':
+ sb.append("\\r");
+ break;
+ case '\t':
+ sb.append("\\t");
+ break;
+ default:
+ if (c < 0x20) {
+ sb.append(String.format("\\%03o", (int) c));
+ } else if (c < 0x7f || Character.isLetter(c)) {
+ sb.append(c);
+ } else {
+ sb.append(String.format("\\u%04x", (int) c));
+ }
+ break;
+ }
+ }
+}
diff --git a/common/src/main/java/com/google/auto/common/AnnotationValues.java b/common/src/main/java/com/google/auto/common/AnnotationValues.java
index 0712e56a..a3cc9495 100644
--- a/common/src/main/java/com/google/auto/common/AnnotationValues.java
+++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java
@@ -512,5 +512,22 @@ public final class AnnotationValues {
return ANNOTATION_VALUES_VISITOR.visit(value);
}
+ /**
+ * Returns a string representation of the given annotation value, suitable for inclusion in a Java
+ * source file as part of an annotation. For example, if {@code annotationValue} represents the
+ * string {@code unchecked} in the annotation {@code @SuppressWarnings("unchecked")}, this method
+ * will return the string {@code "unchecked"}, which you can then use as part of an annotation
+ * being generated.
+ *
+ * <p>For all annotation values other than nested annotations, the returned string can also be
+ * used to initialize a variable of the appropriate type.
+ *
+ * <p>Fully qualified names are used for types in annotations, class literals, and enum constants,
+ * ensuring that the source form will compile without requiring additional imports.
+ */
+ public static String toString(AnnotationValue annotationValue) {
+ return AnnotationOutput.toString(annotationValue);
+ }
+
private AnnotationValues() {}
}
diff --git a/common/src/main/java/com/google/auto/common/Overrides.java b/common/src/main/java/com/google/auto/common/Overrides.java
index 775c304a..cdcd741d 100644
--- a/common/src/main/java/com/google/auto/common/Overrides.java
+++ b/common/src/main/java/com/google/auto/common/Overrides.java
@@ -117,7 +117,7 @@ abstract class Overrides {
// can't be overridden.
return false;
}
- if (!(overridden.getEnclosingElement() instanceof TypeElement)) {
+ if (!MoreElements.isType(overridden.getEnclosingElement())) {
return false;
// We don't know how this could happen but we avoid blowing up if it does.
}
@@ -170,9 +170,14 @@ abstract class Overrides {
return false;
}
} else {
- return in.getKind().isInterface();
- // Method mI in or inherited by interface I (JLS 9.4.1.1). We've already checked everything.
+ // Method mI in or inherited by interface I (JLS 9.4.1.1). We've already checked everything,
+ // except that `overrider` must also be in a subinterface of `overridden`.
// If this is not an interface then we don't know what it is so we say no.
+ TypeElement overriderType = MoreElements.asType(overrider.getEnclosingElement());
+ return in.getKind().isInterface()
+ && typeUtils.isSubtype(
+ typeUtils.erasure(overriderType.asType()),
+ typeUtils.erasure(overriddenType.asType()));
}
}
diff --git a/common/src/main/java/com/google/auto/common/Visibility.java b/common/src/main/java/com/google/auto/common/Visibility.java
index 36f4ad6d..db15f8bd 100644
--- a/common/src/main/java/com/google/auto/common/Visibility.java
+++ b/common/src/main/java/com/google/auto/common/Visibility.java
@@ -16,10 +16,10 @@
package com.google.auto.common;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Comparators.min;
import static javax.lang.model.element.ElementKind.PACKAGE;
import com.google.common.base.Enums;
+import com.google.common.collect.Ordering;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
@@ -77,7 +77,9 @@ public enum Visibility {
Visibility effectiveVisibility = PUBLIC;
Element currentElement = element;
while (currentElement != null) {
- effectiveVisibility = min(effectiveVisibility, ofElement(currentElement));
+ // NOTE: We don't use Guava's Comparators.min() because that requires Guava 30, which would
+ // make this library unusable in annotation processors using Bazel < 5.0.
+ effectiveVisibility = Ordering.natural().min(effectiveVisibility, ofElement(currentElement));
currentElement = currentElement.getEnclosingElement();
}
return effectiveVisibility;