blob: b8825dd013bf72002d108afd2dcf0bcb3909d3af [file] [log] [blame]
/*
* 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 groovy.test.GroovyTestCase
import org.codehaus.groovy.ast.AnnotatedNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.control.MultipleCompilationErrorsException
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.AnnotationCollectorTransform
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
class AnnotationCollectorTest extends GroovyTestCase {
static class MyProcessor extends AnnotationCollectorTransform {
List<AnnotationNode> visit(AnnotationNode collector, AnnotationNode aliasAnnotationUsage, AnnotatedNode aliasAnnotated, SourceUnit source) {
def excludes = aliasAnnotationUsage.getMember("excludes")
if (excludes) {
addError("use myex instead of excludes", aliasAnnotationUsage, source)
return []
}
def myex = aliasAnnotationUsage.getMembers().remove("myex")
if (myex) aliasAnnotationUsage.addMember("excludes", myex)
return super.visit(collector, aliasAnnotationUsage, aliasAnnotated, source)
}
}
void assertScript(String script) {
GroovyShell shell = new GroovyShell(this.class.classLoader)
shell.evaluate(script, getTestClassName())
}
void shouldNotCompile(String script, Closure failureAction) {
GroovyShell shell = new GroovyShell(this.class.classLoader)
try {
shell.parse(script, getTestClassName())
assert false
} catch (MultipleCompilationErrorsException mce) {
failureAction(mce)
}
}
void testSimpleUsage() {
def data = PreCompiledAlias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 0
assert data instanceof Object[][]
assertScript '''
import groovy.transform.PreCompiledAlias
@PreCompiledAlias
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 3
assert new Foo(a: 1, b: 2).toString() == "Foo(1, 2)"
assert PreCompiledAlias.CollectorHelper.value().length == 0
assert PreCompiledAlias.CollectorHelper.value() instanceof Object[][]
'''
assertScript '''
import groovy.transform.*
@AnnotationCollector([ToString, EqualsAndHashCode, Sortable])
@interface NotPreCompiledAlias {}
@NotPreCompiledAlias
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 3
assert new Foo(a: 1, b: 2).toString() == "Foo(1, 2)"
def data = NotPreCompiledAlias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 0
assert data instanceof Object[][]
'''
}
void testUsageWithArgument() {
assertScript '''
import groovy.transform.*
@PreCompiledAlias(excludes=["a"])
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 3
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
assert PreCompiledAlias.CollectorHelper.value().length == 0
assert PreCompiledAlias.CollectorHelper.value() instanceof Object[][]
'''
assertScript '''
import groovy.transform.*
@AnnotationCollector([ToString, EqualsAndHashCode, Sortable])
@interface NotPreCompiledAlias {}
@NotPreCompiledAlias(excludes=["a"])
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 3
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
def data = NotPreCompiledAlias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 0
assert data instanceof Object[][]
'''
}
void testClosureAnnotation() {
assertScript '''
import groovy.transform.*
@AnnotationCollector([ConditionalInterrupt])
@interface NotPreCompiledAlias {}
@NotPreCompiledAlias(applyToAllClasses=false, value={ counter++> 10})
class X {
def counter = 0
def method() {
4.times {null}
}
}
def x = new X(counter:20)
try {
x.method()
assert false
} catch (InterruptedException ie) {
assert true
}
def data = NotPreCompiledAlias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 0
assert data instanceof Object[][]
'''
assertScript '''
import groovy.transform.*
@OtherPreCompiledAlias(applyToAllClasses=false, value={ counter++> 10})
class X {
def counter = 0
def method() {
4.times {null}
}
}
def x = new X(counter:20)
try {
x.method()
assert false
} catch (InterruptedException ie) {
assert true
}
assert OtherPreCompiledAlias.CollectorHelper.value().length == 0
assert OtherPreCompiledAlias.CollectorHelper.value() instanceof Object[][]
'''
}
void testAST() {
assertScript '''
import groovy.transform.*
@AnnotationCollector([ToString, EqualsAndHashCode, Sortable])
@interface Alias {}
@Alias(excludes=["a"])
@ASTTest(phase=org.codehaus.groovy.control.CompilePhase.INSTRUCTION_SELECTION, value={
def annotations = node.annotations
assert annotations.size() == 4 //ASTTest + 3
annotations.each {
assert it.lineNumber == 6 || it.classNode.name.contains("ASTTest")
}
})
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 4
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
def data = Alias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 0
assert data instanceof Object[][]
'''
}
void testConflictingAnnotations() {
shouldNotCompile '''
import groovy.transform.*
@interface ConflictingA {String foo()}
@interface ConflictingB {int foo()}
@AnnotationCollector([ConflictingA, ConflictingB])
@interface Alias {}
@Alias(foo="1") class X{}
''', { exception ->
exception.message.contains("line 9, column 24")
exception.message.contains("Attribute 'foo' should have type 'java.lang.Integer'")
}
}
void testCustomProcessor() {
assertScript '''
import groovy.transform.*
@AnnotationCollector(value=[ToString, EqualsAndHashCode, Sortable], processor='groovy.transform.AnnotationCollectorTest$MyProcessor')
@interface Alias {}
@Alias(myex=["a"])
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 3
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
def data = Alias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 0
assert data instanceof Object[][]
'''
}
void testProcessorThrowingCustomMessage() {
shouldNotCompile '''
import groovy.transform.*
@AnnotationCollector(value=[ToString, EqualsAndHashCode, Sortable], processor='groovy.transform.AnnotationCollectorTest$MyProcessor')
@interface Alias {}
@Alias(excludes=["a"])
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 3
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
''', { ex ->
assert ex.message.contains("use myex instead of excludes @ line 6, column 13")
}
}
void testWrongProcessorName() {
shouldNotCompile '''
import groovy.transform.*
@AnnotationCollector(value=[ToString, EqualsAndHashCode, Sortable], processor='MyProcessor')
@interface Alias {}
@Alias(excludes=["a"])
class Foo {
Integer a, b
}
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")
}
}
void testAnnotationOnAnnotation() {
assertScript '''
import groovy.transform.*
@PreCompiledAlias3
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 2
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
def data = PreCompiledAlias3.CollectorHelper.value()
assert data.length == 2
assert data instanceof Object[][]
assert data[0].length == 2
assert data[0][0] == groovy.transform.Sortable
assert data[0][1] instanceof Map
assert data[0][1].size() == 0
assert data[1][0] == groovy.transform.ToString
assert data[1][1] instanceof Map
assert data[1][1].size() == 1
assert data[1][1].excludes instanceof Object[]
assert data[1][1].excludes[0] == "a"
'''
assertScript '''
import groovy.transform.*
@Sortable
@ToString(excludes=["a"])
@AnnotationCollector()
class Alias {}
@Alias
class Foo {
Integer a, b
}
assert Foo.class.annotations.size() == 2
assert new Foo(a: 1, b: 2).toString() == "Foo(2)"
def data = Alias.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 2
assert data instanceof Object[][]
assert data[0].length == 2
assert data[0][0] == groovy.transform.Sortable
assert data[0][1] instanceof Map
assert data[0][1].size() == 0
assert data[1][0] == groovy.transform.ToString
assert data[1][1] instanceof Map
assert data[1][1].size() == 1
assert data[1][1].excludes instanceof Object[]
assert data[1][1].excludes[0] == "a"
'''
}
void testAnnotationTakingAnnotationParams() {
assertScript '''
import groovy.transform.*
@TheSuperGroovyHeroes
class Team {}
assert Team.class.annotations.size() == 1
assert Team.class.annotations[0] instanceof GroovyCoreTeam
assert Team.class.annotations[0].value().size() == 4
assert Team.class.annotations[0].value().collect { it.value() } == ['Paul', 'Cedric', 'Jochen', 'Guillaume']
def data = TheSuperGroovyHeroes.CollectorHelper.value()
assert data.length == 1
assert data instanceof Object[][]
assert data[0].length == 2
assert data[0][0] == groovy.transform.GroovyCoreTeam
assert data[0][1] instanceof Map
assert data[0][1].size() == 1
data = data[0][1].value
assert data.length == 4
assert data[0][0] == GroovyDeveloper
assert data[0][1].value == "Paul"
assert data[1][0] == GroovyDeveloper
assert data[1][1].value == "Cedric"
assert data[2][0] == GroovyDeveloper
assert data[2][1].value == "Jochen"
assert data[3][0] == GroovyDeveloper
assert data[3][1].value == "Guillaume"
'''
assertScript '''
import groovy.transform.*
@GroovyCoreTeam([
@GroovyDeveloper('Paul'),
@GroovyDeveloper('Cedric'),
@GroovyDeveloper('Jochen'),
@GroovyDeveloper('Guillaume')
])
@AnnotationCollector
@interface SuperHeroes {}
@SuperHeroes
class Team {}
assert Team.class.annotations.size() == 1
assert Team.class.annotations[0] instanceof GroovyCoreTeam
assert Team.class.annotations[0].value().size() == 4
assert Team.class.annotations[0].value().collect { it.value() } == ['Paul', 'Cedric', 'Jochen', 'Guillaume']
def data = SuperHeroes.getAnnotation(AnnotationCollector).serializeClass().value()
assert data.length == 1
assert data instanceof Object[][]
assert data[0].length == 2
assert data[0][0] == groovy.transform.GroovyCoreTeam
assert data[0][1] instanceof Map
assert data[0][1].size() == 1
data = data[0][1].value
assert data.length == 4
assert data[0][0] == GroovyDeveloper
assert data[0][1].value == "Paul"
assert data[1][0] == GroovyDeveloper
assert data[1][1].value == "Cedric"
assert data[2][0] == GroovyDeveloper
assert data[2][1].value == "Jochen"
assert data[3][0] == GroovyDeveloper
assert data[3][1].value == "Guillaume"
'''
}
void testAnnotationCollectorModePreferCollector() {
assertScript '''
import groovy.transform.*
@ToString(includeNames=true)
@AnnotationCollector(mode=AnnotationCollectorMode.PREFER_COLLECTOR)
@interface ToStringNames {}
@ToString(excludes='prop1')
@ToStringNames(excludes='prop2')
class Dummy1 { String prop1, prop2 }
@ToString(excludes='prop1')
@ToStringNames
class Dummy2 { String prop1, prop2 }
assert new Dummy1(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy1(prop1:hello)'
assert new Dummy2(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy2(prop1:hello, prop2:goodbye)'
'''
}
void testAnnotationCollectorModePreferCollectorMerged() {
assertScript '''
import groovy.transform.*
@ToString(includeNames=true)
@AnnotationCollector(mode=AnnotationCollectorMode.PREFER_COLLECTOR_MERGED)
@interface ToStringNames {}
@ToString(excludes='prop1')
@ToStringNames(excludes='prop2')
class Dummy1 { String prop1, prop2 }
@ToString(excludes='prop1')
@ToStringNames
class Dummy2 { String prop1, prop2 }
assert new Dummy1(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy1(prop1:hello)'
assert new Dummy2(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy2(prop2:goodbye)'
'''
}
void testAnnotationCollectorModePreferCollectorExplicit() {
assertScript '''
import groovy.transform.*
@ToString(includeNames=true)
@AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT)
@interface ToStringNames {}
@ToString(excludes='prop1')
@ToStringNames(excludes='prop2')
class Dummy1 { String prop1, prop2 }
@ToString(excludes='prop1')
@ToStringNames
class Dummy2 { String prop1, prop2 }
assert new Dummy1(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy1(goodbye)'
assert new Dummy2(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy2(goodbye)'
'''
}
void testAnnotationCollectorModePreferCollectorExplicitMerged() {
assertScript '''
import groovy.transform.*
@ToString(includeNames=true)
@AnnotationCollector(mode=AnnotationCollectorMode.PREFER_EXPLICIT_MERGED)
@interface ToStringNames {}
@ToString(excludes='prop1')
@ToStringNames(excludes='prop2')
class Dummy1 { String prop1, prop2 }
@ToString(excludes='prop1')
@ToStringNames
class Dummy2 { String prop1, prop2 }
assert new Dummy1(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy1(prop2:goodbye)'
assert new Dummy2(prop1: 'hello', prop2: 'goodbye').toString() == 'Dummy2(prop2:goodbye)'
'''
}
}
@AnnotationCollector([ToString, EqualsAndHashCode, Sortable])
@interface PreCompiledAlias {}
@AnnotationCollector([ConditionalInterrupt])
@interface OtherPreCompiledAlias {}
@Sortable
@ToString(excludes = ["a"])
@AnnotationCollector()
class PreCompiledAlias3 {}
@Retention(RetentionPolicy.RUNTIME)
@interface GroovyCoreTeam {
GroovyDeveloper[] value()
}
@Retention(RetentionPolicy.RUNTIME)
@interface GroovyDeveloper {
String value() default "";
}
@GroovyCoreTeam([
@GroovyDeveloper('Paul'),
@GroovyDeveloper('Cedric'),
@GroovyDeveloper('Jochen'),
@GroovyDeveloper('Guillaume')
])
@AnnotationCollector
@interface TheSuperGroovyHeroes {}