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/04/18 14:15:25 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10570: `@AnnotationCollector`: better error for missing `value()`

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

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


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new c782a96b4d GROOVY-10570: `@AnnotationCollector`: better error for missing `value()`
c782a96b4d is described below

commit c782a96b4dda777de8002995e9937392f2abd147
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Apr 15 11:02:26 2022 -0500

    GROOVY-10570: `@AnnotationCollector`: better error for missing `value()`
---
 .../transform/AnnotationCollectorTransform.java    | 39 +++++++++++++---------
 .../transform/AnnotationCollectorTest.groovy       | 31 ++++++++++++++++-
 src/test/groovy/transform/Groovy10570.java         | 27 +++++++++++++++
 src/test/groovy/transform/Groovy10570emu.java      | 34 +++++++++++++++++++
 4 files changed, 115 insertions(+), 16 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/AnnotationCollectorTransform.java b/src/main/java/org/codehaus/groovy/transform/AnnotationCollectorTransform.java
index 18b7b31be3..1eaa870932 100644
--- a/src/main/java/org/codehaus/groovy/transform/AnnotationCollectorTransform.java
+++ b/src/main/java/org/codehaus/groovy/transform/AnnotationCollectorTransform.java
@@ -18,6 +18,7 @@
  */
 package org.codehaus.groovy.transform;
 
+import groovy.lang.GroovyRuntimeException;
 import groovy.transform.AnnotationCollector;
 import org.apache.groovy.ast.tools.ClassNodeUtils;
 import org.codehaus.groovy.GroovyBugError;
@@ -40,6 +41,7 @@ import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.control.SourceUnit;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -268,36 +270,43 @@ public class AnnotationCollectorTransform {
         }
     }
 
-    private static List<AnnotationNode> getTargetListFromClass(ClassNode alias) {
-        alias = getSerializeClass(alias);
-        Class<?> c = alias.getTypeClass();
+    private static List<AnnotationNode> getTargetListFromClass(final ClassNode alias) {
+        ClassNode cn = getSerializeClass(alias);
+        Class<?> c = cn.getTypeClass();
         Object[][] data;
         try {
             Method m = c.getMethod("value");
+            if (!Modifier.isStatic(m.getModifiers()))
+                throw new NoSuchMethodException("non-static value()");
+
             data = (Object[][]) m.invoke(null);
+            return makeListOfAnnotations(data);
+        } catch (NoSuchMethodException | ClassCastException e) {
+            throw new GroovyRuntimeException("Expecting static method `Object[][] value()`" +
+                    " in " + cn.toString(false) + ". Was it compiled from a Java source?");
         } catch (Exception e) {
             throw new GroovyBugError(e);
         }
-        return makeListOfAnnotations(data);
     }
 
     // 2.5.3 and above gets from annotation attribute otherwise self
-    private static ClassNode getSerializeClass(ClassNode alias) {
-        List<AnnotationNode> annotations = alias.getAnnotations(ClassHelper.make(AnnotationCollector.class));
-        if (!annotations.isEmpty()) {
-            AnnotationNode annotationNode = annotations.get(0);
-            Expression member = annotationNode.getMember("serializeClass");
-            if (member instanceof ClassExpression) {
-                ClassExpression ce = (ClassExpression) member;
-                if (!ce.getType().getName().equals(AnnotationCollector.class.getName())) {
-                    alias = ce.getType();
+    private static ClassNode getSerializeClass(final ClassNode alias) {
+        List<AnnotationNode> collectors = alias.getAnnotations(ClassHelper.make(AnnotationCollector.class));
+        if (!collectors.isEmpty()) {
+            assert collectors.size() == 1;
+            AnnotationNode collectorNode = collectors.get(0);
+            Expression serializeClass = collectorNode.getMember("serializeClass");
+            if (serializeClass instanceof ClassExpression) {
+                ClassNode serializeClassType = serializeClass.getType();
+                if (!serializeClassType.getName().equals(AnnotationCollector.class.getName())) {
+                    return serializeClassType;
                 }
             }
         }
         return alias;
     }
 
-    private static List<AnnotationNode> makeListOfAnnotations(Object[][] data) {
+    private static List<AnnotationNode> makeListOfAnnotations(final Object[][] data) {
         if (data.length == 0) {
             return Collections.emptyList();
         }
@@ -321,7 +330,7 @@ public class AnnotationCollectorTransform {
         return ret;
     }
 
-    private static Expression makeExpression(Object o) {
+    private static Expression makeExpression(final Object o) {
         if (o instanceof Class) {
             return new ClassExpression(ClassHelper.make((Class<?>) o));
         }
diff --git a/src/test/groovy/transform/AnnotationCollectorTest.groovy b/src/test/groovy/transform/AnnotationCollectorTest.groovy
index 86968306a2..3af76052ef 100644
--- a/src/test/groovy/transform/AnnotationCollectorTest.groovy
+++ b/src/test/groovy/transform/AnnotationCollectorTest.groovy
@@ -257,10 +257,39 @@ class AnnotationCollectorTest extends GroovyTestCase {
             assert Foo.class.annotations.size() == 3
             assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
         ''', { ex ->
-            assert ex.message.contains("Could not find class for Transformation Processor MyProcessor declared by Alias")
+            assert ex.message.contains('Could not find class for Transformation Processor MyProcessor declared by Alias')
         }
     }
 
+    // GROOVY-10570
+    void testCollectorOnJavaAnno() {
+        shouldNotCompile '''
+            @groovy.transform.Groovy10570 // Java @interface with @AnnotationCollector
+            class Foo {
+                def bar
+            }
+        ''', { ex ->
+            assert ex.message.contains('Expecting static method `Object[][] value()` in groovy.transform.Groovy10570. Was it compiled from a Java source?')
+        }
+    }
+
+    // GROOVY-10570
+    void testCollectorOnJavaAnno2() {
+        assertScript '''
+            @groovy.transform.Groovy10570emu // Java @interface with @AnnotationCollector and value array
+            class Foo {
+                def bar
+            }
+            assert Foo.class.annotations.size() == 1
+            assert Foo.class.annotations[0].annotationType().name == 'groovy.transform.EqualsAndHashCode'
+
+            // test application of "@EqualsAndHashCode(canEqual=false)"
+            groovy.test.GroovyAssert.shouldFail NoSuchMethodException,{
+                Foo.class.getDeclaredMethod('canEqual', Object)
+            }
+        '''
+    }
+
     void testAnnotationOnAnnotation() {
         assertScript '''
             import groovy.transform.*
diff --git a/src/test/groovy/transform/Groovy10570.java b/src/test/groovy/transform/Groovy10570.java
new file mode 100644
index 0000000000..6e99249e83
--- /dev/null
+++ b/src/test/groovy/transform/Groovy10570.java
@@ -0,0 +1,27 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import java.lang.annotation.*;
+
+@EqualsAndHashCode
+@AnnotationCollector
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Groovy10570 {  }
diff --git a/src/test/groovy/transform/Groovy10570emu.java b/src/test/groovy/transform/Groovy10570emu.java
new file mode 100644
index 0000000000..de9ce9d073
--- /dev/null
+++ b/src/test/groovy/transform/Groovy10570emu.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.transform;
+
+import java.lang.annotation.*;
+
+import static java.util.Collections.singletonMap;
+
+@AnnotationCollector(serializeClass=Groovy10570emu.Data.class)
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Groovy10570emu {
+    class Data {
+        public static Object[][] value() {
+            return new Object[][] {{EqualsAndHashCode.class, singletonMap("useCanEqual", Boolean.FALSE)}};
+        }
+    }
+}