You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/11/20 21:21:16 UTC

[groovy] branch master updated: GROOVY-9541: determine retention policy of annotation container

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new bc0a240bc8 GROOVY-9541: determine retention policy of annotation container
bc0a240bc8 is described below

commit bc0a240bc801f31833e80ed01d9c1a27d25fe984
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Nov 20 14:58:11 2022 -0600

    GROOVY-9541: determine retention policy of annotation container
---
 .../codehaus/groovy/classgen/ExtendedVerifier.java | 55 ++++++++++++----------
 src/test/gls/annotations/AnnotationTest.groovy     | 26 +++++++++-
 2 files changed, 54 insertions(+), 27 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
index 1dcefc1ede..1319c1e829 100644
--- a/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/ExtendedVerifier.java
@@ -34,7 +34,7 @@ import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
 import org.codehaus.groovy.ast.expr.ClassExpression;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
-import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.ReturnStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
@@ -44,15 +44,14 @@ import org.codehaus.groovy.control.SourceUnit;
 import org.objectweb.asm.Opcodes;
 
 import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
+import static java.util.stream.Collectors.toList;
 import static org.codehaus.groovy.ast.AnnotationNode.ANNOTATION_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.CONSTRUCTOR_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.FIELD_TARGET;
@@ -64,7 +63,9 @@ import static org.codehaus.groovy.ast.AnnotationNode.RECORD_COMPONENT_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.TYPE_PARAMETER_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.TYPE_TARGET;
 import static org.codehaus.groovy.ast.AnnotationNode.TYPE_USE_TARGET;
+import static org.codehaus.groovy.ast.ClassHelper.makeCached;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getInterfacesAndSuperInterfaces;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.listX;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
@@ -274,9 +275,8 @@ public class ExtendedVerifier extends ClassCodeVisitorSupport {
         }
         this.currentClass.setAnnotated(true);
         Map<String, List<AnnotationNode>> nonSourceAnnotations = new LinkedHashMap<>();
-        boolean skippable = node.getNodeMetaData("_SKIPPABLE_ANNOTATIONS") != null;
-        Iterator<AnnotationNode> iterator = annotations.iterator();
-        while (iterator.hasNext()) {
+        boolean skippable = Boolean.TRUE.equals(node.getNodeMetaData("_SKIPPABLE_ANNOTATIONS"));
+        for (Iterator<AnnotationNode> iterator = annotations.iterator(); iterator.hasNext(); ) {
             AnnotationNode unvisited = iterator.next();
             AnnotationNode visited;
             {
@@ -345,10 +345,10 @@ public class ExtendedVerifier extends ClassCodeVisitorSupport {
     }
 
     private void processDuplicateAnnotationContainers(AnnotatedNode node, Map<String, List<AnnotationNode>> nonSourceAnnotations) {
-        for (Map.Entry<String, List<AnnotationNode>> next : nonSourceAnnotations.entrySet()) {
-            if (next.getValue().size() > 1) {
+        for (Map.Entry<String, List<AnnotationNode>> entry : nonSourceAnnotations.entrySet()) {
+            if (entry.getValue().size() > 1) {
                 ClassNode repeatable = null;
-                AnnotationNode repeatee = next.getValue().get(0);
+                AnnotationNode repeatee = entry.getValue().get(0);
                 for (AnnotationNode anno : repeatee.getClassNode().getAnnotations()) {
                     if (anno.getClassNode().getName().equals("java.lang.annotation.Repeatable")) {
                         Expression value = anno.getMember("value");
@@ -360,29 +360,32 @@ public class ExtendedVerifier extends ClassCodeVisitorSupport {
                 }
                 if (repeatable != null) {
                     if (nonSourceAnnotations.containsKey(repeatable.getName())) {
-                        addError("Cannot specify duplicate annotation on the same member. Explicit " + repeatable.getName()
-                                + " found when creating implicit container for " + next.getKey(), node);
+                        addError("Cannot specify duplicate annotation on the same member. Explicit " + repeatable.getName() + " found when creating implicit container for " + entry.getKey(), node);
                     }
                     AnnotationNode collector = new AnnotationNode(repeatable);
-                    if (repeatee.hasRuntimeRetention()) {
+                    if (repeatee.hasClassRetention()) {
+                        collector.setClassRetention(true);
+                    } else if (repeatee.hasRuntimeRetention()) {
                         collector.setRuntimeRetention(true);
-                    } else if (repeatable.isResolved()) {
-                        Class<?> repeatableType = repeatable.getTypeClass();
-                        Retention retention = repeatableType.getAnnotation(Retention.class);
-                        collector.setRuntimeRetention(retention != null && retention.value().equals(RetentionPolicy.RUNTIME));
-                    } else {
-                        for (AnnotationNode annotation : repeatable.getAnnotations()) {
-                            if (annotation.getClassNode().getName().equals("java.lang.annotation.Retention")) {
-                                Expression value = annotation.getMember("value"); assert value != null;
-                                Object retention = evaluateExpression(value, source.getConfiguration());
-                                collector.setRuntimeRetention(retention != null && retention.toString().equals("RUNTIME"));
-                                break;
+                    } else { // load retention policy from annotation definition
+                        List<AnnotationNode> retention = repeatable.getAnnotations(makeCached(Retention.class));
+                        if (!retention.isEmpty()) {
+                            Object policy;
+                            Expression value = retention.get(0).getMember("value");
+                            if (value instanceof PropertyExpression) {
+                                policy = ((PropertyExpression) value).getPropertyAsString();
+                            } else { // NOTE: it is risky to evaluate the expression from repeatable's source this way:
+                                policy = evaluateExpression(value, source.getConfiguration(), source.getClassLoader());
+                            }
+                            if ("CLASS".equals(policy)) {
+                                collector.setClassRetention(true);
+                            } else if ("RUNTIME".equals(policy)) {
+                                collector.setRuntimeRetention(true);
                             }
                         }
                     }
-                    collector.addMember("value", new ListExpression(next.getValue().stream()
-                        .map(AnnotationConstantExpression::new).collect(Collectors.toList())));
-                    node.getAnnotations().removeAll(next.getValue());
+                    collector.addMember("value", listX(entry.getValue().stream().map(AnnotationConstantExpression::new).collect(toList())));
+                    node.getAnnotations().removeAll(entry.getValue());
                     node.addAnnotation(collector);
                 }
             }
diff --git a/src/test/gls/annotations/AnnotationTest.groovy b/src/test/gls/annotations/AnnotationTest.groovy
index e10abf4f3b..ed5adac997 100644
--- a/src/test/gls/annotations/AnnotationTest.groovy
+++ b/src/test/gls/annotations/AnnotationTest.groovy
@@ -996,6 +996,31 @@ final class AnnotationTest {
         '''
     }
 
+    @Test
+    void testAnnotationWithRepeatableSupported2() {
+        for (policy in ['CLASS', 'SOURCE']) {
+            assertScript shell, """
+                @Retention(RetentionPolicy.$policy)
+                @Repeatable(B)
+                @interface A {
+                    String value() default "foo"
+                }
+
+                @Retention(RetentionPolicy.$policy)
+                @interface B {
+                }
+
+                @A @A('bar')
+                class C {
+                }
+
+                // not available at run-time
+                assert C.getAnnotationsByType(A).length == 0
+                assert C.getAnnotationsByType(B).length == 0
+            """
+        }
+    }
+
     @Test // GROOVY-9452
     void testDuplicationAnnotationOnClassWithParams() {
         def err = shouldFail shell, '''
@@ -1016,7 +1041,6 @@ final class AnnotationTest {
             @As([@A("c")])
             class Foo {}
         '''
-
         assert err =~ /Cannot specify duplicate annotation/
     }