From a6c0b10704311c94c179136b13a4dcc244e8011f Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Thu, 18 Jan 2024 19:04:26 +0000 Subject: [PATCH] 8323684: TypeMirror.{getAnnotationsByType, getAnnotation} return uninformative results Reviewed-by: jjg --- .../com/sun/tools/javac/code/Type.java | 16 +-- .../processing/model/type/BasicAnnoTests.java | 134 +++++++++++++++++- 2 files changed, 132 insertions(+), 18 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index e295096bb6d..533d756d641 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2024, 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 @@ -465,20 +465,6 @@ public abstract class Type extends AnnoConstruct implements TypeMirror, PoolCons return getMetadata(TypeMetadata.Annotations.class, Annotations::annotations, List.nil()); } - - @Override @DefinedBy(Api.LANGUAGE_MODEL) - public A getAnnotation(Class annotationType) { - return null; - } - - - @Override @DefinedBy(Api.LANGUAGE_MODEL) - public A[] getAnnotationsByType(Class annotationType) { - @SuppressWarnings("unchecked") - A[] tmp = (A[]) java.lang.reflect.Array.newInstance(annotationType, 0); - return tmp; - } - /** Return the base types of a list of types. */ public static List baseTypes(List ts) { diff --git a/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java b/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java index 219896c3305..d45edc8e3d6 100644 --- a/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java +++ b/test/langtools/tools/javac/processing/model/type/BasicAnnoTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8013852 8031744 8225377 + * @bug 8013852 8031744 8225377 8323684 * @summary Annotations on types * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -42,10 +42,13 @@ import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Target; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.AbstractMap; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Map; import java.util.NavigableMap; import java.util.Set; @@ -86,6 +89,23 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { DPrinter dprinter; PrintWriter out; boolean verbose = true; + // Use a compile-time mapping to avoid repeated runtime reflective lookups. + private static final Map> nameToAnnotation = + Map.ofEntries(new NameToAnnotationEntry("java.lang.Override", Override.class), + new NameToAnnotationEntry("java.lang.annotation.Repeatable", Repeatable.class), + new NameToAnnotationEntry("java.lang.annotation.Target", Target.class), + new NameToAnnotationEntry("BasicAnnoTests.Test", BasicAnnoTests.Test.class), + new NameToAnnotationEntry("BasicAnnoTests.Tests",BasicAnnoTests.Tests.class), + new NameToAnnotationEntry("BasicAnnoTests.TA", BasicAnnoTests.TA.class), + new NameToAnnotationEntry("BasicAnnoTests.TB", BasicAnnoTests.TB.class), + new NameToAnnotationEntry("BasicAnnoTests.TC", BasicAnnoTests.TC.class), + new NameToAnnotationEntry("BasicAnnoTests.TCs", BasicAnnoTests.TCs.class)); + + static class NameToAnnotationEntry extends AbstractMap.SimpleEntry> { + public NameToAnnotationEntry(String key, Class entry) { + super(key, entry); + } + } @Override public void init(ProcessingEnvironment pEnv) { @@ -104,7 +124,7 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { } void error(Element e, String msg) { - messager.printMessage(Kind.ERROR, msg, e); + messager.printError(msg, e); errors++; } @@ -263,6 +283,7 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { */ static AnnotationMirror getAnnotation(AnnotatedConstruct e, String name) { for (AnnotationMirror m: e.getAnnotationMirrors()) { + checkAnnotatedConstructConsistency(e, m); TypeElement te = (TypeElement) m.getAnnotationType().asElement(); if (te.getQualifiedName().contentEquals(name)) { return m; @@ -276,6 +297,7 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { List res = new ArrayList<>(); for (AnnotationMirror m : e.getAnnotationMirrors()) { + checkAnnotatedConstructConsistency(e, m); TypeElement te = (TypeElement) m.getAnnotationType().asElement(); if (te.getQualifiedName().contentEquals(name)) { Compound theAnno = (Compound)m; @@ -290,6 +312,85 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { return res; } + /** + * Verify that an annotation mirror returned by + * getAnnotationMirrors() has a matching annotation from + * getAnnotation and appropriate values are returned by + * getAnnotationsByType. + */ + static void checkAnnotatedConstructConsistency(AnnotatedConstruct ac, AnnotationMirror m) { + // For each annotation mirror present, an annotation of the + // same annotation type should be directly present as well. + String annotationTypeName = ((TypeElement)m.getAnnotationType().asElement()).getQualifiedName().toString(); + var annotationClass = Objects.requireNonNull(nameToAnnotation.get(annotationTypeName)); + Annotation a = ac.getAnnotation(annotationClass); + Objects.requireNonNull(a, "Annotation " + m + " not found from getAnnotation(" + annotationTypeName + ")"); + + // For non-repeating annotation types, getAnnotationsByType should + // wrap a single annotation in an one-element array. + Annotation[] aArray = ac.getAnnotationsByType(annotationClass); + if (aArray.length != 1) { + throw new RuntimeException("Annotation " + m + + " not found from getAnnotationsByType(" + annotationTypeName + ")"); + } + + // For a container annotation, getAnnotationsByType should + // "look through" and return the contained annotations by + // their annotation class. + var containedAnnotationClass = containerFor(annotationClass); + if (containedAnnotationClass != null) { + Object wrappedAnnotationValue = extractAnnotationValue(a); + int wrappedCount = -1; + if (wrappedAnnotationValue instanceof Annotation[] wrapped) { + wrappedCount = wrapped.length; + } + + Annotation[] containedAnnotations = ac.getAnnotationsByType(containedAnnotationClass); + // Check number of contained annotations for consistency + // Given annotation type A and container annotation type As, the result of + // annotatedConstruct.getAnnotaton(As.class).value().length == + // annotatedConstruct.getAnnotationsByType(A.class).length + // More thorough checking could be done as well, checking + // the types or value of the constituent annotations/AnnotationMirrors. + Object annotationValue = extractAnnotationValue(a); + if (annotationValue instanceof Annotation[] objects + && objects.length != wrappedCount) { + throw new RuntimeException("\t\t\tmismatched array length : " + objects.length + + "\twrapped count" + wrappedCount); + } + } + } + + static Object extractAnnotationValue(Annotation annotation) { + var annotationClass = annotation.annotationType(); + try { + // Call value method of the annotation; expected to return + // an array of the contained annotation type. + Method valueMethod = annotationClass.getMethod("value", null); + return valueMethod.invoke(annotation); + } catch (ReflectiveOperationException roe) { + throw new RuntimeException(roe); + } + } + + static int wrappedAnnotationCount(Annotation a) { + Object value = extractAnnotationValue(a); + if (value instanceof Annotation[] annotationValueArray) { + return annotationValueArray.length; + } else { + return -1; + } + } + + static Class containerFor(Class possibleContainer) { + // Could write this more generally; for now, hard-code particular cases + if (possibleContainer == TCs.class) + return TC.class; + if (possibleContainer == Tests.class) + return Tests.class; + return null; + } + /** * Get a specific value from an annotation mirror. */ @@ -402,11 +503,23 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { public @interface TA { int value(); } + @Target(ElementType.TYPE_USE) public @interface TB { int value(); } + @Target(ElementType.TYPE_USE) + @Repeatable(TCs.class) + public @interface TC { + int value(); + } + + @Target(ElementType.TYPE_USE) + @interface TCs { + TC[] value(); + } + // Test cases // TODO: add more cases for arrays @@ -473,6 +586,21 @@ public class BasicAnnoTests extends JavacTestingAbstractProcessor { @Test(posn=1, annoType=TA.class, expect="3") public @TA(3) int @TB(33) [] f3; + @Test(posn=0, annoType=TC.class, expect="1") + public @TC(1) int f1_repeat0; + + // Containter annotation @TCs({@TC(2), @TC(3)}) created by the compiler + @Test(posn=0, annoType=TCs.class, expect="{@BasicAnnoTests.TC(2), @BasicAnnoTests.TC(3)}") + public @TC(2) @TC(3) int f1_repeat1; + + // Use container explicitly + @Test(posn=0, annoType=TCs.class, expect="{@BasicAnnoTests.TC(4)}") + public @TCs(@TC(4)) int f1_repeat2; + + // Explicit empty container + @Test(posn=0, annoType=TCs.class, expect="{}") + public @TCs({}) int f1_repeat3; + @Test(posn=3, annoType=TA.class, expect="4") public int m1(@TA(4) float a) throws Exception { return 0; }