--- old/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java 2016-05-24 00:15:21.265929589 -0700 +++ new/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java 2016-05-24 00:15:21.181929591 -0700 @@ -27,6 +27,8 @@ 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; @@ -92,6 +94,38 @@ Set 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 getElementsAnnotatedWithAny(TypeElement... annotations){ + // Use LinkedHashSet rather than HashSet for predictability + Set 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 * package elements and type elements included in this @@ -110,4 +144,36 @@ * represent an annotation type */ Set getElementsAnnotatedWith(Class 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 getElementsAnnotatedWithAny(Set> annotations){ + // Use LinkedHashSet rather than HashSet for predictability + Set result = new LinkedHashSet<>(); + for (Class annotation : annotations) { + result.addAll(getElementsAnnotatedWith(annotation)); + } + return Collections.unmodifiableSet(result); + } } --- old/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacRoundEnvironment.java 2016-05-24 00:15:21.553929585 -0700 +++ new/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacRoundEnvironment.java 2016-05-24 00:15:21.465929586 -0700 @@ -51,6 +51,7 @@ 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 rootElements; @@ -63,6 +64,7 @@ this.errorRaised = errorRaised; this.rootElements = rootElements; this.processingEnv = processingEnv; + this.eltUtils = processingEnv.getElementUtils(); } public String toString() { @@ -117,10 +119,9 @@ */ @DefinedBy(Api.ANNOTATION_PROCESSING) public Set getElementsAnnotatedWith(TypeElement a) { - Set result = Collections.emptySet(); - if (a.getKind() != ElementKind.ANNOTATION_TYPE) - throw new IllegalArgumentException(NOT_AN_ANNOTATION_TYPE + a); + throwIfNotAnnotation(a); + Set result = Collections.emptySet(); ElementScanner9, TypeElement> scanner = new AnnotationSetScanner(result); @@ -130,34 +131,60 @@ return result; } - // Could be written as a local class inside getElementsAnnotatedWith - private class AnnotationSetScanner extends - ElementScanner9, TypeElement> { - // Insertion-order preserving set - Set annotatedElements = new LinkedHashSet<>(); + @DefinedBy(Api.ANNOTATION_PROCESSING) + public Set getElementsAnnotatedWithAny(TypeElement... annotations) { + Set annotationSet = new LinkedHashSet<>(annotations.length); + for (TypeElement annotation : annotations) { + throwIfNotAnnotation(annotation); + annotationSet.add(annotation); + } - AnnotationSetScanner(Set defaultSet) { - super(defaultSet); + Set result = Collections.emptySet(); + ElementScanner9, Set> scanner = + new AnnotationSetMultiScanner(result); + + for (Element element : rootElements) + result = scanner.scan(element, annotationSet); + + return result; + } + + private static abstract class ElementScanningIncludingTypeParameters + extends ElementScanner9 { + + protected ElementScanningIncludingTypeParameters(R defaultValue) { + super(defaultValue); } @Override @DefinedBy(Api.LANGUAGE_MODEL) - public Set visitType(TypeElement e, TypeElement p) { + 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 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); } + } + + // Could be written as a local class inside getElementsAnnotatedWith + private class AnnotationSetScanner extends + ElementScanningIncludingTypeParameters, TypeElement> { + // Insertion-order preserving set + Set annotatedElements = new LinkedHashSet<>(); + + AnnotationSetScanner(Set defaultSet) { + super(defaultSet); + } @Override @DefinedBy(Api.LANGUAGE_MODEL) public Set scan(Element e, TypeElement p) { java.util.List annotationMirrors = - processingEnv.getElementUtils().getAllAnnotationMirrors(e); + eltUtils.getAllAnnotationMirrors(e); for (AnnotationMirror annotationMirror : annotationMirrors) { if (p.equals(annotationMirror.getAnnotationType().asElement())) annotatedElements.add(e); @@ -167,22 +194,68 @@ } } + private class AnnotationSetMultiScanner extends + ElementScanningIncludingTypeParameters, Set> { + // Insertion-order preserving set + Set annotatedElements = new LinkedHashSet<>(); + + AnnotationSetMultiScanner(Set defaultSet) { + super(defaultSet); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Set scan(Element e, Set p) { + java.util.List annotationMirrors = + eltUtils.getAllAnnotationMirrors(e); + for (AnnotationMirror annotationMirror : annotationMirrors) { + if (p.contains(annotationMirror.getAnnotationType().asElement())) + annotatedElements.add(e); + } + e.accept(this, p); + return annotatedElements; + } + } + /** * {@inheritDoc} */ @DefinedBy(Api.ANNOTATION_PROCESSING) public Set getElementsAnnotatedWith(Class 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 getElementsAnnotatedWithAny(Set> annotations) { + List annotationsAsElements = new ArrayList<>(annotations.size()); + + for (Class 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 void throwIfNotAnnotation(Class 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); + } } --- old/test/tools/javac/processing/environment/round/TestElementsAnnotatedWith.java 2016-05-24 00:15:21.833929580 -0700 +++ new/test/tools/javac/processing/environment/round/TestElementsAnnotatedWith.java 2016-05-24 00:15:21.745929582 -0700 @@ -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,174 @@ 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 annotations, - RoundEnvironment roundEnvironment) { - TypeElement annotatedElementInfoElement = - elements.getTypeElement("AnnotatedElementInfo"); - Set resultsMeta = Collections.emptySet(); - Set resultsBase = Collections.emptySet(); + RoundEnvironment roundEnv) { + // First check sets of annotated elements using the round + // environment from the annotation processing tool framework. + checkSetOfAnnotatedElements(roundEnv); + + // 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; + } + + 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 getElementsAnnotatedWith(Class a) { + return re.getElementsAnnotatedWith(a); + } + + @Override + public Set getElementsAnnotatedWithAny(Set> a) { + // Default method defined in the interface + return RoundEnvironment.super.getElementsAnnotatedWithAny(a); + } + + @Override + public Set getElementsAnnotatedWith(TypeElement a) { + return re.getElementsAnnotatedWith(a); + } + + @Override + public Set getElementsAnnotatedWithAny(TypeElement... a) { + // Default method defined in the interface + return RoundEnvironment.super.getElementsAnnotatedWithAny(a); + } - if (!roundEnvironment.processingOver()) { - testNonAnnotations(roundEnvironment); + @Override + public Set 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 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 resultsMeta = Collections.emptySet(); + Set resultsMetaAny = Collections.emptySet(); + Set resultsMetaMulti = new HashSet<>(); + Set resultsMetaAnyMulti = Collections.emptySet(); + Set resultsBase = Collections.emptySet(); + Set resultsBaseAny = Collections.emptySet(); + Set 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. - - Set expectedNames = new HashSet(Arrays.asList(annotatedElementInfo.names())); - - resultsMeta = - roundEnvironment. - getElementsAnnotatedWith(elements.getTypeElement(annotatedElementInfo.annotationName())); - - if (!resultsMeta.isEmpty()) - System.err.println("Results: " + resultsMeta); - - 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."); - } + Objects.requireNonNull(annotatedElemInfo, + "Missing AnnotatedElementInfo annotation on " + firstType); + + // Verify that the annotation information is as expected. + Set expectedNames = + new HashSet<>(Arrays.asList(annotatedElemInfo.names())); + + String annotationName = annotatedElemInfo.annotationName(); + TypeElement annotationTypeElem = elements.getTypeElement(annotationName); + + resultsMeta = re.getElementsAnnotatedWith(annotationTypeElem); + resultsMetaAny = re.getElementsAnnotatedWithAny(annotationTypeElem); + resultsMetaMulti.addAll(resultsMeta); + resultsMetaMulti.addAll(re.getElementsAnnotatedWith(annotatedElemInfoElem)); + resultsMetaAnyMulti = re.getElementsAnnotatedWithAny(annotationTypeElem, annotatedElemInfoElem); + + 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> 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 +226,53 @@ "\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"); + } + } + private void throwOnNonEmpty(Set results, String message) { + if (!results.isEmpty()) { + throw new RuntimeException("Nonempty " + message + "\t" + results); } - return true; } - private Set computeResultsBase(RoundEnvironment roundEnvironment, String name) { + private Set 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 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 +281,28 @@ * Verify non-annotation types result in * IllegalArgumentExceptions. */ - private void testNonAnnotations(RoundEnvironment roundEnvironment) { + private void testNonAnnotations(RoundEnvironment roundEnv) { + Class objectClass = (Class)Object.class; + Set elements; + try { + elements = roundEnv.getElementsAnnotatedWith(objectClass); + throw new RuntimeException("Illegal argument exception not thrown"); + } catch (IllegalArgumentException iae) {} + + try { + elements = roundEnv.getElementsAnnotatedWithAny(Set.of(objectClass)); + throw new RuntimeException("Illegal argument exception not thrown"); + } catch (IllegalArgumentException iae) {} + + TypeElement objectElement = processingEnv.getElementUtils().getTypeElement("java.lang.Object"); try { - Set elements = roundEnvironment.getElementsAnnotatedWith((Class)Object.class ); + elements = roundEnv.getElementsAnnotatedWith(objectElement); throw new RuntimeException("Illegal argument exception not thrown"); - } catch(IllegalArgumentException iae) {} + } catch (IllegalArgumentException iae) {} try { - Set elements = - roundEnvironment.getElementsAnnotatedWith(processingEnv. - getElementUtils(). - getTypeElement("java.lang.Object") ); + elements = roundEnv.getElementsAnnotatedWithAny(objectElement); throw new RuntimeException("Illegal argument exception not thrown"); - } catch(IllegalArgumentException iae) {} + } catch (IllegalArgumentException iae) {} } }