Skip to content
Permalink
Browse files
GROOVY-10570: @AnnotationCollector: better error for missing value()
  • Loading branch information
eric-milles committed Apr 18, 2022
1 parent 5c10ca3 commit 0a4579946f4f70b3e17b2b9ed321a671c7f2401b
Showing 4 changed files with 115 additions and 16 deletions.
@@ -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.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 @@ private static void copyMembers(final Map<String, Expression> members, final Ann
}
}

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 @@ private static List<AnnotationNode> makeListOfAnnotations(Object[][] data) {
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));
}
@@ -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.*
@@ -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 { }
@@ -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)}};
}
}
}

0 comments on commit 0a45799

Please sign in to comment.