8032230: Enhance javax.a.p.RoundEnvironment after repeating annotations

Reviewed-by: jjg
This commit is contained in:
Joe Darcy 2016-05-25 22:32:16 -07:00
parent de6399c1a3
commit fff165ddd1
3 changed files with 378 additions and 83 deletions

View File

@ -27,6 +27,8 @@ package javax.annotation.processing;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.LinkedHashSet;
import java.util.Collections;
import java.util.Set;
import java.lang.annotation.Annotation;
@ -91,6 +93,38 @@ public interface RoundEnvironment {
*/
Set<? extends Element> getElementsAnnotatedWith(TypeElement a);
/**
* Returns the elements annotated with one or more of the given
* annotation types.
*
* @apiNote This method may be useful when processing repeating
* annotations by looking for an annotation type and its
* containing annotation type at the same time.
*
* @implSpec The default implementation of this method creates an
* empty result set, iterates over the annotations in the argument
* set calling {@link #getElementsAnnotatedWith(TypeElement)} on
* each annotation and adding those results to the result
* set. Finally, the contents of the result set are returned as an
* unmodifiable set.
*
* @param annotations annotation types being requested
* @return the elements annotated with one or more of the given
* annotation types, or an empty set if there are none
* @throws IllegalArgumentException if the any elements of the
* argument set do not represent an annotation type
* @jls 9.6.3 Repeatable Annotation Types
* @since 9
*/
default Set<? extends Element> getElementsAnnotatedWithAny(TypeElement... annotations){
// Use LinkedHashSet rather than HashSet for predictability
Set<Element> result = new LinkedHashSet<>();
for (TypeElement annotation : annotations) {
result.addAll(getElementsAnnotatedWith(annotation));
}
return Collections.unmodifiableSet(result);
}
/**
* Returns the elements annotated with the given annotation type.
* The annotation may appear directly or be inherited. Only
@ -110,4 +144,36 @@ public interface RoundEnvironment {
* represent an annotation type
*/
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
/**
* Returns the elements annotated with one or more of the given
* annotation types.
*
* @apiNote This method may be useful when processing repeating
* annotations by looking for an annotation type and its
* containing annotation type at the same time.
*
* @implSpec The default implementation of this method creates an
* empty result set, iterates over the annotations in the argument
* set calling {@link #getElementsAnnotatedWith(Class)} on
* each annotation and adding those results to the result
* set. Finally, the contents of the result set are returned as an
* unmodifiable set.
*
* @param annotations annotation types being requested
* @return the elements annotated with one or more of the given
* annotation types, or an empty set if there are none
* @throws IllegalArgumentException if the any elements of the
* argument set do not represent an annotation type
* @jls 9.6.3 Repeatable Annotation Types
* @since 9
*/
default Set<? extends Element> getElementsAnnotatedWithAny(Set<Class<? extends Annotation>> annotations){
// Use LinkedHashSet rather than HashSet for predictability
Set<Element> result = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : annotations) {
result.addAll(getElementsAnnotatedWith(annotation));
}
return Collections.unmodifiableSet(result);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -51,6 +51,7 @@ public class JavacRoundEnvironment implements RoundEnvironment {
private final boolean processingOver;
private final boolean errorRaised;
private final ProcessingEnvironment processingEnv;
private final Elements eltUtils;
// Caller must pass in an immutable set
private final Set<? extends Element> rootElements;
@ -63,6 +64,7 @@ public class JavacRoundEnvironment implements RoundEnvironment {
this.errorRaised = errorRaised;
this.rootElements = rootElements;
this.processingEnv = processingEnv;
this.eltUtils = processingEnv.getElementUtils();
}
public String toString() {
@ -100,9 +102,6 @@ public class JavacRoundEnvironment implements RoundEnvironment {
return rootElements;
}
private static final String NOT_AN_ANNOTATION_TYPE =
"The argument does not represent an annotation type: ";
/**
* Returns the elements annotated with the given annotation type.
* Only type elements <i>included</i> in this round of annotation
@ -117,10 +116,9 @@ public class JavacRoundEnvironment implements RoundEnvironment {
*/
@DefinedBy(Api.ANNOTATION_PROCESSING)
public Set<? extends Element> getElementsAnnotatedWith(TypeElement a) {
Set<Element> result = Collections.emptySet();
if (a.getKind() != ElementKind.ANNOTATION_TYPE)
throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a);
throwIfNotAnnotation(a);
Set<Element> result = Collections.emptySet();
ElementScanner9<Set<Element>, TypeElement> scanner =
new AnnotationSetScanner(result);
@ -130,41 +128,93 @@ public class JavacRoundEnvironment implements RoundEnvironment {
return result;
}
@DefinedBy(Api.ANNOTATION_PROCESSING)
public Set<? extends Element> getElementsAnnotatedWithAny(TypeElement... annotations) {
// Don't bother to special-case annotations.length == 1 as
// return getElementsAnnotatedWith(annotations[0]);
Set<TypeElement> annotationSet = new LinkedHashSet<>(annotations.length);
for (TypeElement annotation : annotations) {
throwIfNotAnnotation(annotation);
annotationSet.add(annotation);
}
Set<Element> result = Collections.emptySet();
ElementScanner9<Set<Element>, Set<TypeElement>> scanner =
new AnnotationSetMultiScanner(result);
for (Element element : rootElements)
result = scanner.scan(element, annotationSet);
return result;
}
// Could be written as a local class inside getElementsAnnotatedWith
private class AnnotationSetScanner extends
ElementScanner9<Set<Element>, TypeElement> {
ElementScanningIncludingTypeParameters<Set<Element>, TypeElement> {
// Insertion-order preserving set
Set<Element> annotatedElements = new LinkedHashSet<>();
private Set<Element> annotatedElements = new LinkedHashSet<>();
AnnotationSetScanner(Set<Element> defaultSet) {
super(defaultSet);
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Set<Element> visitType(TypeElement e, TypeElement p) {
public Set<Element> scan(Element e, TypeElement annotation) {
for (AnnotationMirror annotMirror : eltUtils.getAllAnnotationMirrors(e)) {
if (annotation.equals(mirrorAsElement(annotMirror))) {
annotatedElements.add(e);
break;
}
}
e.accept(this, annotation);
return annotatedElements;
}
}
// Could be written as a local class inside getElementsAnnotatedWithAny
private class AnnotationSetMultiScanner extends
ElementScanningIncludingTypeParameters<Set<Element>, Set<TypeElement>> {
// Insertion-order preserving set
private Set<Element> annotatedElements = new LinkedHashSet<>();
AnnotationSetMultiScanner(Set<Element> defaultSet) {
super(defaultSet);
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Set<Element> scan(Element e, Set<TypeElement> annotations) {
for (AnnotationMirror annotMirror : eltUtils.getAllAnnotationMirrors(e)) {
if (annotations.contains(mirrorAsElement(annotMirror))) {
annotatedElements.add(e);
break;
}
}
e.accept(this, annotations);
return annotatedElements;
}
}
private static abstract class ElementScanningIncludingTypeParameters<R, P>
extends ElementScanner9<R, P> {
protected ElementScanningIncludingTypeParameters(R defaultValue) {
super(defaultValue);
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public R visitType(TypeElement e, P p) {
// Type parameters are not considered to be enclosed by a type
scan(e.getTypeParameters(), p);
return super.visitType(e, p);
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Set<Element> visitExecutable(ExecutableElement e, TypeElement p) {
public R visitExecutable(ExecutableElement e, P p) {
// Type parameters are not considered to be enclosed by an executable
scan(e.getTypeParameters(), p);
return super.visitExecutable(e, p);
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Set<Element> scan(Element e, TypeElement p) {
java.util.List<? extends AnnotationMirror> annotationMirrors =
processingEnv.getElementUtils().getAllAnnotationMirrors(e);
for (AnnotationMirror annotationMirror : annotationMirrors) {
if (p.equals(annotationMirror.getAnnotationType().asElement()))
annotatedElements.add(e);
}
e.accept(this, p);
return annotatedElements;
}
}
/**
@ -172,17 +222,48 @@ public class JavacRoundEnvironment implements RoundEnvironment {
*/
@DefinedBy(Api.ANNOTATION_PROCESSING)
public Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a) {
if (!a.isAnnotation())
throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a);
throwIfNotAnnotation(a);
String name = a.getCanonicalName();
if (name == null)
return Collections.emptySet();
else {
TypeElement annotationType = processingEnv.getElementUtils().getTypeElement(name);
TypeElement annotationType = eltUtils.getTypeElement(name);
if (annotationType == null)
return Collections.emptySet();
else
return getElementsAnnotatedWith(annotationType);
}
}
@DefinedBy(Api.ANNOTATION_PROCESSING)
public Set<? extends Element> getElementsAnnotatedWithAny(Set<Class<? extends Annotation>> annotations) {
List<TypeElement> annotationsAsElements = new ArrayList<>(annotations.size());
for (Class<? extends Annotation> annotation : annotations) {
throwIfNotAnnotation(annotation);
String name = annotation.getCanonicalName();
if (name == null)
continue;
annotationsAsElements.add(eltUtils.getTypeElement(name));
}
return getElementsAnnotatedWithAny(annotationsAsElements.toArray(new TypeElement[0]));
}
private Element mirrorAsElement(AnnotationMirror annotationMirror) {
return annotationMirror.getAnnotationType().asElement();
}
private static final String NOT_AN_ANNOTATION_TYPE =
"The argument does not represent an annotation type: ";
private void throwIfNotAnnotation(Class<? extends Annotation> a) {
if (!a.isAnnotation())
throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a);
}
private void throwIfNotAnnotation(TypeElement a) {
if (a.getKind() != ElementKind.ANNOTATION_TYPE)
throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a);
}
}

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 6397298 6400986 6425592 6449798 6453386 6508401 6498938 6911854 8030049 8038080
* @bug 6397298 6400986 6425592 6449798 6453386 6508401 6498938 6911854 8030049 8038080 8032230
* @summary Tests that getElementsAnnotatedWith works properly.
* @author Joseph D. Darcy
* @library /tools/javac/lib
@ -51,69 +51,183 @@ import java.util.Collections;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.processing.*;
import javax.lang.model.element.*;
import static javax.lang.model.util.ElementFilter.*;
/**
* This processor verifies that the information returned by
* getElementsAnnotatedWith is consistent with the expected results
* stored in an AnnotatedElementInfo annotation.
* getElementsAnnotatedWith and getElementsAnnotatedWithAny is
* consistent with the expected results stored in an
* AnnotatedElementInfo annotation.
*/
@AnnotatedElementInfo(annotationName="java.lang.SuppressWarnings", expectedSize=0, names={})
public class TestElementsAnnotatedWith extends JavacTestingAbstractProcessor {
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnvironment) {
TypeElement annotatedElementInfoElement =
elements.getTypeElement("AnnotatedElementInfo");
Set<? extends Element> resultsMeta = Collections.emptySet();
Set<? extends Element> resultsBase = Collections.emptySet();
RoundEnvironment roundEnv) {
// First check sets of annotated elements using the round
// environment from the annotation processing tool framework.
checkSetOfAnnotatedElements(roundEnv);
if (!roundEnvironment.processingOver()) {
testNonAnnotations(roundEnvironment);
// Next check sets of annotated elements using a round
// environment which uses the default implementations of the
// getElementsAnnotatedWithAny methods from the interface.
checkSetOfAnnotatedElements(new TestingRoundEnvironment(roundEnv));
return true;
}
/**
* To allow testing of the executable code of the default methods
* for the two overloaded getElementsAnnotatedWithAny methods
* defined in the RoundEnvironment interface, this class delegates
* the non-default methods of RoundEnvironment to a given
* RoundEnvironment object and then explicitly calls the default
* methods of the interface instead of relying on the object's
* implementation of those methods.
*/
private class TestingRoundEnvironment implements RoundEnvironment {
private RoundEnvironment re;
public TestingRoundEnvironment(RoundEnvironment re) {
this.re = re;
}
@Override
public boolean errorRaised() {
return re.errorRaised();
}
@Override
public Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a) {
return re.getElementsAnnotatedWith(a);
}
@Override
public Set<? extends Element> getElementsAnnotatedWithAny(Set<Class<? extends Annotation>> a) {
// Default method defined in the interface
return RoundEnvironment.super.getElementsAnnotatedWithAny(a);
}
@Override
public Set<? extends Element> getElementsAnnotatedWith(TypeElement a) {
return re.getElementsAnnotatedWith(a);
}
@Override
public Set<? extends Element> getElementsAnnotatedWithAny(TypeElement... a) {
// Default method defined in the interface
return RoundEnvironment.super.getElementsAnnotatedWithAny(a);
}
@Override
public Set<? extends Element> getRootElements() {
return re.getRootElements();
}
@Override
public boolean processingOver() {
return re.processingOver();
}
}
/**
* The method checks the following conditions:
*
* 1) The sets of elements found are equal for the TypeElement and
* Class<? extends Annotation> methods on logically equivalent
* arguments.
*
* 2) getElementsAnnotatedWithAny(X) is equal to
* getElementsAnnotatedWith(X') where X is a set/var-args array
* with one element and X' is the element.
*
* 3) Verify the result of getElementsAnnotatedWithAny({X, Y}) is equal to
* getElementsAnnotatedWith(X) UNION getElementsAnnotatedWith(Y).
*/
void checkSetOfAnnotatedElements(RoundEnvironment re) {
TypeElement annotatedElemInfoElem = elements.getTypeElement("AnnotatedElementInfo");
// For the "Any" methods, search for both the expected
// annotation and AnnotatedElementInfo and verify the return
// set is the union of searching for AnnotatedElementInfo and
// the other annotation
Set<? extends Element> resultsMeta = Collections.emptySet();
Set<? extends Element> resultsMetaAny = Collections.emptySet();
Set<Element> resultsMetaMulti = new HashSet<>();
Set<? extends Element> resultsMetaAnyMulti = Collections.emptySet();
Set<? extends Element> resultsBase = Collections.emptySet();
Set<? extends Element> resultsBaseAny = Collections.emptySet();
Set<? extends Element> resultsBaseAnyMulti = Collections.emptySet();
if (!re.processingOver()) {
testNonAnnotations(re);
// Verify AnnotatedElementInfo is present on the first
// specified type.
TypeElement firstType = typesIn(roundEnvironment.getRootElements()).iterator().next();
TypeElement firstType = typesIn(re.getRootElements()).iterator().next();
AnnotatedElementInfo annotatedElementInfo = firstType.getAnnotation(AnnotatedElementInfo.class);
AnnotatedElementInfo annotatedElemInfo =
firstType.getAnnotation(AnnotatedElementInfo.class);
boolean failed = false;
if (annotatedElementInfo == null)
throw new IllegalArgumentException("Missing AnnotatedElementInfo annotation on " +
firstType);
else {
// Verify that the annotation information is as
// expected.
Objects.requireNonNull(annotatedElemInfo,
"Missing AnnotatedElementInfo annotation on " + firstType);
Set<String> expectedNames = new HashSet<String>(Arrays.asList(annotatedElementInfo.names()));
// Verify that the annotation information is as expected.
Set<String> expectedNames =
new HashSet<>(Arrays.asList(annotatedElemInfo.names()));
resultsMeta =
roundEnvironment.
getElementsAnnotatedWith(elements.getTypeElement(annotatedElementInfo.annotationName()));
String annotationName = annotatedElemInfo.annotationName();
TypeElement annotationTypeElem = elements.getTypeElement(annotationName);
if (!resultsMeta.isEmpty())
System.err.println("Results: " + resultsMeta);
resultsMeta = re.getElementsAnnotatedWith(annotationTypeElem);
resultsMetaAny = re.getElementsAnnotatedWithAny(annotationTypeElem);
resultsMetaMulti.addAll(resultsMeta);
resultsMetaMulti.addAll(re.getElementsAnnotatedWith(annotatedElemInfoElem));
resultsMetaAnyMulti = re.getElementsAnnotatedWithAny(annotationTypeElem, annotatedElemInfoElem);
if (resultsMeta.size() != annotatedElementInfo.expectedSize()) {
failed = true;
System.err.printf("Bad number of elements; expected %d, got %d%n",
annotatedElementInfo.expectedSize(), resultsMeta.size());
} else {
for(Element element : resultsMeta) {
String simpleName = element.getSimpleName().toString();
if (!expectedNames.contains(simpleName) ) {
failed = true;
System.err.println("Name ``" + simpleName + "'' not expected.");
}
if (!resultsMeta.isEmpty())
System.err.println("Results: " + resultsMeta);
if (!resultsMeta.equals(resultsMetaAny)) {
failed = true;
System.err.printf("Inconsistent Meta with vs withAny results");
}
if (resultsMeta.size() != annotatedElemInfo.expectedSize()) {
failed = true;
System.err.printf("Bad number of elements; expected %d, got %d%n",
annotatedElemInfo.expectedSize(), resultsMeta.size());
} else {
for(Element element : resultsMeta) {
String simpleName = element.getSimpleName().toString();
if (!expectedNames.contains(simpleName) ) {
failed = true;
System.err.println("Name ``" + simpleName + "'' not expected.");
}
}
}
resultsBase = computeResultsBase(roundEnvironment, annotatedElementInfo.annotationName());
resultsBase = computeResultsBase(re, annotationName);
resultsBaseAny = computeResultsBaseAny(re, annotationName);
try {
Set<Class<? extends Annotation>> tmp = new HashSet<>();
tmp.add(AnnotatedElementInfo.class);
tmp.add(Class.forName(annotationName).asSubclass(Annotation.class));
resultsBaseAnyMulti = re.getElementsAnnotatedWithAny(tmp);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
if (!resultsBase.equals(resultsBaseAny)) {
failed = true;
System.err.printf("Inconsistent Base with vs withAny results");
}
if (!resultsMeta.equals(resultsBase)) {
failed = true;
@ -121,29 +235,53 @@ public class TestElementsAnnotatedWith extends JavacTestingAbstractProcessor {
"\nbase: " + resultsBase);
}
if (!resultsMetaAnyMulti.equals(resultsMetaMulti)) {
failed = true;
System.err.println("MetaMultAny and MetaMulti sets unequal;\n meta: " + resultsMeta +
"\nbase: " + resultsBase);
}
if (!resultsBaseAnyMulti.equals(resultsMetaAnyMulti)) {
failed = true;
System.err.println("BaseMulti and MetaMulti sets unequal;\n meta: " + resultsMeta +
"\nbase: " + resultsBase);
}
if (failed) {
System.err.println("AnnotatedElementInfo: " + annotatedElementInfo);
System.err.println("AnnotatedElementInfo: " + annotatedElemInfo);
throw new RuntimeException();
}
} else {
// If processing is over without an error, the specified
// elements should be empty so an empty set should be returned.
resultsMeta = roundEnvironment.getElementsAnnotatedWith(annotatedElementInfoElement);
resultsBase = roundEnvironment.getElementsAnnotatedWith(AnnotatedElementInfo.class);
if (!resultsMeta.isEmpty())
throw new RuntimeException("Nonempty resultsMeta: " + resultsMeta);
if (!resultsBase.isEmpty())
throw new RuntimeException("Nonempty resultsBase: " + resultsBase);
// elements should be empty so an empty set should be
// returned.
throwOnNonEmpty(re.getElementsAnnotatedWith(annotatedElemInfoElem), "resultsMeta");
throwOnNonEmpty(re.getElementsAnnotatedWithAny(annotatedElemInfoElem), "resultsMetaAny");
throwOnNonEmpty(re.getElementsAnnotatedWith(AnnotatedElementInfo.class), "resultsBase");
throwOnNonEmpty(re.getElementsAnnotatedWithAny(Set.of(AnnotatedElementInfo.class)), "resultsBaseAny");
}
return true;
}
private Set<? extends Element> computeResultsBase(RoundEnvironment roundEnvironment, String name) {
private void throwOnNonEmpty(Set<? extends Element> results, String message) {
if (!results.isEmpty()) {
throw new RuntimeException("Nonempty " + message + "\t" + results);
}
}
private Set<? extends Element> computeResultsBase(RoundEnvironment roundEnv, String name) {
try {
return roundEnvironment.
return roundEnv.
getElementsAnnotatedWith(Class.forName(name).asSubclass(Annotation.class));
} catch(ClassNotFoundException cnfe) {
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
}
private Set<? extends Element> computeResultsBaseAny(RoundEnvironment roundEnv, String name) {
try {
return roundEnv.
getElementsAnnotatedWithAny(Set.of(Class.forName(name).asSubclass(Annotation.class)));
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException(cnfe);
}
}
@ -152,18 +290,28 @@ public class TestElementsAnnotatedWith extends JavacTestingAbstractProcessor {
* Verify non-annotation types result in
* IllegalArgumentExceptions.
*/
private void testNonAnnotations(RoundEnvironment roundEnvironment) {
private void testNonAnnotations(RoundEnvironment roundEnv) {
Class objectClass = (Class)Object.class;
Set<? extends Element> elements;
try {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith((Class)Object.class );
elements = roundEnv.getElementsAnnotatedWith(objectClass);
throw new RuntimeException("Illegal argument exception not thrown");
} catch(IllegalArgumentException iae) {}
} catch (IllegalArgumentException iae) {}
try {
Set<? extends Element> elements =
roundEnvironment.getElementsAnnotatedWith(processingEnv.
getElementUtils().
getTypeElement("java.lang.Object") );
elements = roundEnv.getElementsAnnotatedWithAny(Set.of(objectClass));
throw new RuntimeException("Illegal argument exception not thrown");
} catch(IllegalArgumentException iae) {}
} catch (IllegalArgumentException iae) {}
TypeElement objectElement = processingEnv.getElementUtils().getTypeElement("java.lang.Object");
try {
elements = roundEnv.getElementsAnnotatedWith(objectElement);
throw new RuntimeException("Illegal argument exception not thrown");
} catch (IllegalArgumentException iae) {}
try {
elements = roundEnv.getElementsAnnotatedWithAny(objectElement);
throw new RuntimeException("Illegal argument exception not thrown");
} catch (IllegalArgumentException iae) {}
}
}