You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2017/07/14 03:31:00 UTC

[2/2] groovy git commit: GROOVY-8234: Add @Repeatable java8 annotation support (closes #567)

GROOVY-8234: Add @Repeatable java8 annotation support (closes #567)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/10320bb3
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/10320bb3
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/10320bb3

Branch: refs/heads/GROOVY_2_5_X
Commit: 10320bb327b18b3ba98d4b5c81afcf3362977f51
Parents: e8637cd
Author: paulk <pa...@asert.com.au>
Authored: Mon Jun 26 23:17:12 2017 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Fri Jul 14 13:30:38 2017 +1000

----------------------------------------------------------------------
 .../groovy/classgen/ExtendedVerifier.java       | 72 ++++++++++++++++++--
 src/test/gls/annotations/AnnotationTest.groovy  | 47 +++++++++++++
 2 files changed, 113 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/10320bb3/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java b/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java
index bad5acc..5e13cd7 100644
--- a/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java
+++ b/src/main/org/codehaus/groovy/classgen/ExtendedVerifier.java
@@ -18,8 +18,24 @@
  */
 package org.codehaus.groovy.classgen;
 
-import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.GenericsType;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.PackageNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+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.stmt.ReturnStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.ast.tools.ParameterUtils;
@@ -34,6 +50,7 @@ import org.objectweb.asm.Opcodes;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -135,21 +152,64 @@ public class ExtendedVerifier extends ClassCodeVisitorSupport {
             addError("Annotations are not supported in the current runtime. " + JVM_ERROR_MESSAGE, node);
             return;
         }
+        Map<String, List<AnnotationNode>> runtimeAnnotations = new LinkedHashMap<String, List<AnnotationNode>>();
         for (AnnotationNode unvisited : node.getAnnotations()) {
             AnnotationNode visited = visitAnnotation(unvisited);
-            boolean isTargetAnnotation = visited.getClassNode().isResolved() &&
-                    visited.getClassNode().getName().equals("java.lang.annotation.Target");
+            String name = visited.getClassNode().getName();
+            if (visited.hasRuntimeRetention()) {
+                List<AnnotationNode> seen = runtimeAnnotations.get(name);
+                if (seen == null) {
+                    seen = new ArrayList<AnnotationNode>();
+                }
+                seen.add(visited);
+                runtimeAnnotations.put(name, seen);
+            }
+            boolean isTargetAnnotation = name.equals("java.lang.annotation.Target");
 
             // Check if the annotation target is correct, unless it's the target annotating an annotation definition
             // defining on which target elements the annotation applies
             if (!isTargetAnnotation && !visited.isTargetAllowed(target)) {
-                addError("Annotation @" + visited.getClassNode().getName()
-                        + " is not allowed on element " + AnnotationNode.targetToName(target),
-                        visited);
+                addError("Annotation @" + name + " is not allowed on element "
+                        + AnnotationNode.targetToName(target), visited);
             }
             visitDeprecation(node, visited);
             visitOverride(node, visited);
         }
+        checkForDuplicateAnnotations(node, runtimeAnnotations);
+    }
+
+    private void checkForDuplicateAnnotations(AnnotatedNode node, Map<String, List<AnnotationNode>> runtimeAnnotations) {
+        for (Map.Entry<String, List<AnnotationNode>> next : runtimeAnnotations.entrySet()) {
+            if (next.getValue().size() > 1) {
+                ClassNode repeatable = null;
+                AnnotationNode repeatee = next.getValue().get(0);
+                List<AnnotationNode> repeateeAnnotations = repeatee.getClassNode().getAnnotations();
+                for (AnnotationNode anno : repeateeAnnotations) {
+                    ClassNode annoClassNode = anno.getClassNode();
+                    if (annoClassNode.getName().equals("java.lang.annotation.Repeatable")) {
+                        Expression value = anno.getMember("value");
+                        if (value instanceof ClassExpression) {
+                            ClassExpression ce = (ClassExpression) value;
+                            if (ce.getType() != null && ce.getType().isAnnotationDefinition()) {
+                                repeatable = ce.getType();
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (repeatable != null) {
+                    AnnotationNode collector = new AnnotationNode(repeatable);
+                    collector.setRuntimeRetention(true); // checked earlier
+                    List<Expression> annos = new ArrayList<Expression>();
+                    for (AnnotationNode an : next.getValue()) {
+                        annos.add(new AnnotationConstantExpression(an));
+                    }
+                    collector.addMember("value", new ListExpression(annos));
+                    node.addAnnotation(collector);
+                    node.getAnnotations().removeAll(next.getValue());
+                }
+            }
+        }
     }
 
     private static void visitDeprecation(AnnotatedNode node, AnnotationNode visited) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/10320bb3/src/test/gls/annotations/AnnotationTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/annotations/AnnotationTest.groovy b/src/test/gls/annotations/AnnotationTest.groovy
index af32e9b..f4d3826 100644
--- a/src/test/gls/annotations/AnnotationTest.groovy
+++ b/src/test/gls/annotations/AnnotationTest.groovy
@@ -705,6 +705,53 @@ class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
+    // GROOVY-8234
+    void testAnnotationWithRepeatableSupported() {
+        assertScript '''
+            import java.lang.annotation.*
+
+            class MyClass {
+                private static final expected = '@MyAnnotationArray(value=[@MyAnnotation(value=val1), @MyAnnotation(value=val2)])'
+
+                // control
+                @MyAnnotationArray([@MyAnnotation("val1"), @MyAnnotation("val2")])
+                String method1() { 'method1' }
+
+                // duplicate candidate for auto collection
+                @MyAnnotation(value = "val1")
+                @MyAnnotation(value = "val2")
+                String method2() { 'method2' }
+
+                static void main(String... args) {
+                    MyClass myc = new MyClass()
+                    assert 'method1' == myc.method1()
+                    assert 'method2' == myc.method2()
+                    assert checkAnnos(myc, "method1") == expected
+                    assert checkAnnos(myc, "method2") == expected
+                }
+
+                private static String checkAnnos(MyClass myc, String name) {
+                    def m = myc.getClass().getMethod(name)
+                    List annos = m.getAnnotations()
+                    assert annos.size() == 1
+                    annos[0].toString()
+                }
+            }
+
+            @Target(ElementType.METHOD)
+            @Retention(RetentionPolicy.RUNTIME)
+            @Repeatable(MyAnnotationArray)
+            @interface MyAnnotation {
+                String value() default "val0"
+            }
+
+            @Retention(RetentionPolicy.RUNTIME)
+            @interface MyAnnotationArray {
+                MyAnnotation[] value()
+            }
+        '''
+    }
+
     //Parametrized tests in Spock would allow to make it much more readable
     private static String codeWithMetaAnnotationWithTarget(String targetElementTypeName) {
         """