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/06/10 15:25:50 UTC
[groovy] branch master updated: GROOVY-10654: `ASTTransformationCustomizer`: enum and array parameters
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 8476bd7a7a GROOVY-10654: `ASTTransformationCustomizer`: enum and array parameters
8476bd7a7a is described below
commit 8476bd7a7ab02151ee838651d8a3c92bb87fb8cb
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Jun 10 10:25:33 2022 -0500
GROOVY-10654: `ASTTransformationCustomizer`: enum and array parameters
---
.../customizers/ASTTransformationCustomizer.groovy | 107 +++++++-------
.../ASTTransformationCustomizerTest.groovy | 161 ++++++++++++---------
2 files changed, 152 insertions(+), 116 deletions(-)
diff --git a/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy
index 2ea2539f5a..6c3d3f6ba6 100644
--- a/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy
+++ b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy
@@ -18,15 +18,15 @@
*/
package org.codehaus.groovy.control.customizers
+import groovy.transform.AutoFinal
import groovy.transform.CompilationUnitAware
+import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
-import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.Expression
-import org.codehaus.groovy.ast.expr.ListExpression
import org.codehaus.groovy.classgen.GeneratorContext
import org.codehaus.groovy.control.CompilationUnit
import org.codehaus.groovy.control.CompilePhase
@@ -39,6 +39,8 @@ import java.lang.annotation.Annotation
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.listX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX
/**
* This customizer allows applying an AST transformation to a source unit with
@@ -82,13 +84,13 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
*
* @since 1.8.0
*/
-@CompileStatic
+@AutoFinal @CompileStatic
class ASTTransformationCustomizer extends CompilationCustomizer implements CompilationUnitAware {
- private final AnnotationNode annotationNode
- final ASTTransformation transformation
+ private boolean applied // global xforms
protected CompilationUnit compilationUnit
- private boolean applied = false // used for global AST transformations
+ private final AnnotationNode annotationNode
+ final ASTTransformation transformation
/**
* Creates an AST transformation customizer using the specified annotation. The transformation classloader can
@@ -99,9 +101,9 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
* @param astTransformationClassName
* @param transformationClassLoader
*/
- ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) {
+ ASTTransformationCustomizer(Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) {
super(findPhase(transformationAnnotation, astTransformationClassName, transformationClassLoader))
- final Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader)
+ Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader)
this.transformation = clazz.newInstance()
this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation))
}
@@ -113,7 +115,7 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
* @param transformationAnnotation
* @param astTransformationClassName
*/
- ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) {
+ ASTTransformationCustomizer(Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) {
this(transformationAnnotation, astTransformationClassName, transformationAnnotation.classLoader)
}
@@ -127,15 +129,15 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
* @param astTransformationClassName
* @param transformationClassLoader
*/
- ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) {
+ ASTTransformationCustomizer(Map annotationParams, Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) {
super(findPhase(transformationAnnotation, astTransformationClassName, transformationClassLoader))
- final Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader)
+ Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader)
this.transformation = clazz.newInstance()
this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation))
this.annotationParameters = annotationParams
}
- ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) {
+ ASTTransformationCustomizer(Map annotationParams, Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) {
this(annotationParams, transformationAnnotation, transformationAnnotation.classLoader)
}
@@ -145,9 +147,9 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
* @param transformationAnnotation
* @param transformationClassLoader
*/
- ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) {
+ ASTTransformationCustomizer(Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) {
super(findPhase(transformationAnnotation, transformationClassLoader))
- final Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, transformationClassLoader)
+ Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, transformationClassLoader)
this.transformation = clazz.newInstance()
this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation))
}
@@ -156,14 +158,14 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
* Creates an AST transformation customizer using the specified annotation.
* @param transformationAnnotation
*/
- ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation) {
+ ASTTransformationCustomizer(Class<? extends Annotation> transformationAnnotation) {
this(transformationAnnotation, transformationAnnotation.classLoader)
}
/**
* Creates an AST transformation customizer using the specified transformation.
*/
- ASTTransformationCustomizer(final ASTTransformation transformation) {
+ ASTTransformationCustomizer(ASTTransformation transformation) {
super(findPhase(transformation))
this.transformation = transformation
this.annotationNode = null
@@ -176,19 +178,19 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
* @param transformationAnnotation
* @param transformationClassLoader
*/
- ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) {
+ ASTTransformationCustomizer(Map annotationParams, Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) {
super(findPhase(transformationAnnotation, transformationClassLoader))
- final Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, transformationClassLoader)
+ Class<ASTTransformation> clazz = findASTTransformationClass(transformationAnnotation, transformationClassLoader)
this.transformation = clazz.newInstance()
this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation))
this.annotationParameters = annotationParams
}
- ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation) {
+ ASTTransformationCustomizer(Map annotationParams, Class<? extends Annotation> transformationAnnotation) {
this(annotationParams, transformationAnnotation, transformationAnnotation.classLoader)
}
- ASTTransformationCustomizer(final Map annotationParams, final ASTTransformation transformation) {
+ ASTTransformationCustomizer(Map annotationParams, ASTTransformation transformation) {
this(transformation)
this.annotationParameters = annotationParams
}
@@ -199,7 +201,7 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
@SuppressWarnings('ClassForName')
private static Class<ASTTransformation> findASTTransformationClass(Class<? extends Annotation> anAnnotationClass, ClassLoader transformationClassLoader) {
- final GroovyASTTransformationClass annotation = anAnnotationClass.getAnnotation(GroovyASTTransformationClass)
+ GroovyASTTransformationClass annotation = anAnnotationClass.getAnnotation(GroovyASTTransformationClass)
if (annotation == null) throw new IllegalArgumentException("Provided class doesn't look like an AST @interface")
Class[] classes = annotation.classes()
@@ -217,8 +219,8 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
private static CompilePhase findPhase(ASTTransformation transformation) {
if (transformation == null) throw new IllegalArgumentException('Provided transformation must not be null')
- final Class<?> clazz = transformation.class
- final GroovyASTTransformation annotation = clazz.getAnnotation(GroovyASTTransformation)
+ Class<?> clazz = transformation.class
+ GroovyASTTransformation annotation = clazz.getAnnotation(GroovyASTTransformation)
if (annotation == null) throw new IllegalArgumentException("Provided ast transformation is not annotated with $GroovyASTTransformation.name")
annotation.phase()
@@ -237,57 +239,60 @@ class ASTTransformationCustomizer extends CompilationCustomizer implements Compi
}
/**
- * Specify annotation parameters. For example, if the annotation is :
+ * Specify annotation parameters. For example, if the annotation is:
* <pre>@Log(value='logger')</pre>
* You could create an AST transformation customizer and specify the "value" parameter thanks to this method:
- * <pre>annotationParameters = [value: 'logger']
+ * <pre>annotationParameters = [value: 'logger']</pre>
*
* Note that you cannot specify annotation closure values directly. If the annotation you want to add takes
* a closure as an argument, you will have to set a {@link ClosureExpression} instead. This can be done by either
* creating a custom {@link ClosureExpression} from code, or using the {@link org.codehaus.groovy.ast.builder.AstBuilder}.
- *
- * Here is an example :
+ * <p>
+ * Here is an example:
* <pre>
- * // add @Contract({distance >= 0 })
- * customizer = new ASTTransformationCustomizer(Contract)
- * final expression = new AstBuilder().buildFromCode(CompilePhase.CONVERSION) {->
- * distance >= 0
- * }.expression[0]
- * customizer.annotationParameters = [value: expression]</pre>
+ * // add @Contract({distance >= 0 })
+ * def customizer = new ASTTransformationCustomizer(Contract)
+ * def expression = new AstBuilder().buildFromCode(CompilePhase.CONVERSION) { ->
+ * distance >= 0
+ * }.expression[0]
+ * customizer.annotationParameters = [value: expression]</pre>
*
* @param params the annotation parameters
*
* @since 1.8.1
*/
- @SuppressWarnings('Instanceof')
+ @CompileDynamic
void setAnnotationParameters(Map<String, Object> params) {
- if (params == null || annotationNode == null) return
- params.each { key, value ->
- if (!annotationNode.classNode.getMethod(key)) {
- throw new IllegalArgumentException("${annotationNode.classNode.name} does not accept any [$key] parameter")
+ if (annotationNode == null || params == null || params.isEmpty()) return
+ params.each { name, value ->
+ if (!annotationNode.classNode.getMethod(name)) {
+ throw new IllegalArgumentException("${annotationNode.classNode.name} does not accept any [$name] parameter")
}
if (value instanceof Closure) {
- throw new IllegalArgumentException('Direct usage of closure is not supported by the AST ' +
- 'compilation customizer. Please use ClosureExpression instead.')
- } else if (value instanceof Expression) {
+ throw new IllegalArgumentException('Direct usage of closure is not supported by the AST compilation customizer. Please use ClosureExpression instead.')
+ }
+
+ Expression valueExpression
+
+ if (value instanceof Expression) {
+ valueExpression = value
// avoid NPEs due to missing source code
- value.lineNumber = 0
- value.lastLineNumber = 0
- annotationNode.addMember(key, value)
+ value.lineNumber = 0; value.lastLineNumber = 0
} else if (value instanceof Class) {
- annotationNode.addMember(key, classX(value))
- } else if (value instanceof List) {
- annotationNode.addMember(key, new ListExpression(value.collect {
- it instanceof Class ? classX(it) : constX(it)
- }))
+ valueExpression = classX(value)
+ } else if (value instanceof Enum) {
+ valueExpression = propX(classX(ClassHelper.make(value.getClass())), value.toString())
+ } else if (value instanceof List || value.getClass().isArray()) {
+ valueExpression = listX(value.collect { it instanceof Class ? classX(it) : constX(it) })
} else {
- annotationNode.addMember(key, constX(value))
+ valueExpression = constX(value)
}
+
+ annotationNode.addMember(name, valueExpression)
}
}
@Override
- @SuppressWarnings('Instanceof')
void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
if (transformation instanceof CompilationUnitAware) {
((CompilationUnitAware) transformation).compilationUnit = compilationUnit
diff --git a/src/test/org/codehaus/groovy/control/customizers/ASTTransformationCustomizerTest.groovy b/src/test/org/codehaus/groovy/control/customizers/ASTTransformationCustomizerTest.groovy
index b5e063fedc..0d15d1263c 100644
--- a/src/test/org/codehaus/groovy/control/customizers/ASTTransformationCustomizerTest.groovy
+++ b/src/test/org/codehaus/groovy/control/customizers/ASTTransformationCustomizerTest.groovy
@@ -18,7 +18,6 @@
*/
package org.codehaus.groovy.control.customizers
-import groovy.test.GroovyTestCase
import groovy.transform.TimedInterrupt
import groovy.util.logging.Log
import org.codehaus.groovy.ast.ASTNode
@@ -28,106 +27,113 @@ import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
+import org.junit.Test
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
-import java.util.logging.Logger
+import static groovy.test.GroovyAssert.shouldFail
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX
/**
* Tests the {@link ASTTransformationCustomizer}.
*/
-class ASTTransformationCustomizerTest extends GroovyTestCase {
- CompilerConfiguration configuration
- ASTTransformationCustomizer customizer
-
- void setUp() {
- configuration = new CompilerConfiguration()
- }
+final class ASTTransformationCustomizerTest {
+ @Test
void testLocalTransformation() {
- customizer = new ASTTransformationCustomizer(Log)
- configuration.addCompilationCustomizers(customizer)
- def shell = new GroovyShell(configuration)
- def result = shell.evaluate("""
+ def shell = GroovyShell.withConfig {
+ ast(Log)
+ }
+ def result = shell.evaluate '''
class MyClass {}
new MyClass()
- """)
- assert result.log.class == Logger
+ '''
+ assert result.log.class == java.util.logging.Logger
}
- void testLocalTransformationAndCustomClassLoader() {
- ClassLoader loader = new URLClassLoader([]as URL[]) {
+ @Test
+ void testLocalTransformationClassLoader() {
+ def loader = new URLClassLoader() {
@Override
Class<?> loadClass(String name) {
- null
}
}
shouldFail(ClassNotFoundException) {
- customizer = new ASTTransformationCustomizer(Log, loader)
+ new ASTTransformationCustomizer(Log, loader)
}
}
- void testLocalTransformationWithAnnotationParameter() {
- customizer = new ASTTransformationCustomizer(Log)
- customizer.annotationParameters = [value: 'logger']
- configuration.addCompilationCustomizers(customizer)
- def shell = new GroovyShell(configuration)
- def result = shell.evaluate("""
+ @Test
+ void testLocalTransformationStringParameter() {
+ def shell = GroovyShell.withConfig {
+ ast(Log, value: 'logger')
+ }
+ def result = shell.evaluate '''
class MyClass {}
new MyClass()
- """)
- assert result.logger.class == Logger
+ '''
+ assert result.logger.class == java.util.logging.Logger
}
- void testLocalTransformationWithInvalidAnnotationParameter() {
- customizer = new ASTTransformationCustomizer(Log)
+ @Test
+ void testLocalTransformationUnknownParameter() {
+ def customizer = new ASTTransformationCustomizer(Log)
shouldFail(IllegalArgumentException) {
customizer.annotationParameters = [invalid: 'logger']
}
}
- void testGlobalTransformation() {
- final TestTransformation transformation = new TestTransformation()
- customizer = new ASTTransformationCustomizer(transformation)
- configuration.addCompilationCustomizers(customizer)
- def shell = new GroovyShell(configuration)
- assert shell.evaluate('true')
- assert transformation.applied.get()
- }
-
- void testGlobalTransformation2() {
- final TestTransformation transformation = new TestTransformation()
- customizer = new ASTTransformationCustomizer(transformation)
- configuration.addCompilationCustomizers(customizer)
- def shell = new GroovyShell(configuration)
- assert shell.evaluate("""
- class A {}
- class B {}
- true
- """)
- assert transformation.applied.get()
+ @Test
+ void testLocalTransformationListOfClassParameter() {
+ def shell = GroovyShell.withConfig {
+ ast(Newify, value: [Integer, Long])
+ }
+ def result = shell.evaluate '''
+ Integer(11) + Long(31)
+ '''
+ assert result == 42
}
- void testLocalTransformationWithListOfClassAnnotationParameter() {
- customizer = new ASTTransformationCustomizer(Newify, value: [Integer, Long])
- configuration.addCompilationCustomizers(customizer)
- def shell = new GroovyShell(configuration)
+ @Test
+ void testLocalTransformationArrayOfClassParameter() {
+ def shell = GroovyShell.withConfig {
+ ast(Newify, value: new Class[]{Integer, Long})
+ }
def result = shell.evaluate '''
Integer(11) + Long(31)
'''
assert result == 42
}
- void testAnyExpressionAsParameterValue() {
- customizer = new ASTTransformationCustomizer(value:300, unit: propX(classX(ClassHelper.make(TimeUnit)),'MILLISECONDS'), TimedInterrupt)
- configuration.addCompilationCustomizers(customizer)
- def shell = new GroovyShell(configuration)
- def result = shell.evaluate '''
- import java.util.concurrent.TimeoutException
+ @Test
+ void testLocalTransformationPropertyExpressionParameter() {
+ def shell = GroovyShell.withConfig {
+ ast(TimedInterrupt, value:300, unit:propX(classX(ClassHelper.make(TimeUnit)),'MILLISECONDS'))
+ imports { normal 'java.util.concurrent.TimeoutException' }
+ }
+ assert shell.evaluate('''
+ boolean interrupted = false
+ try {
+ 10.times {
+ sleep 100
+ }
+ } catch (TimeoutException ignore) {
+ interrupted = true
+ }
+ interrupted
+ ''')
+ }
+
+ @Test // GROOVY-10654
+ void testLocalTransformationEnumerationConstantParameter() {
+ def shell = GroovyShell.withConfig {
+ ast(TimedInterrupt, value:300, unit:TimeUnit.MILLISECONDS)
+ imports { normal 'java.util.concurrent.TimeoutException' }
+ }
+ assert shell.evaluate('''
boolean interrupted = false
try {
10.times {
@@ -138,21 +144,46 @@ class ASTTransformationCustomizerTest extends GroovyTestCase {
}
interrupted
- '''
- assert result
+ ''')
+ }
+
+ //--------------------------------------------------------------------------
+
+ @Test
+ void testGlobalTransformation() {
+ TestTransformation transformation = new TestTransformation()
+ def shell = new GroovyShell(new CompilerConfiguration().tap {
+ addCompilationCustomizers(new ASTTransformationCustomizer(transformation))
+ })
+ assert shell.evaluate('true')
+ assert transformation.applied
+ }
+
+ @Test
+ void testGlobalTransformation2() {
+ TestTransformation transformation = new TestTransformation()
+ def shell = new GroovyShell(new CompilerConfiguration().tap {
+ addCompilationCustomizers(new ASTTransformationCustomizer(transformation))
+ })
+ assert shell.evaluate('''
+ class A {}
+ class B {}
+ true
+ ''')
+ assert transformation.applied
}
@GroovyASTTransformation(phase=CompilePhase.CONVERSION)
private static class TestTransformation implements ASTTransformation {
- private AtomicBoolean applied = new AtomicBoolean(false)
+ private final applied = new AtomicBoolean()
+
+ boolean isApplied() { return applied.get() }
void visit(ASTNode[] nodes, SourceUnit source) {
if (applied.getAndSet(true)) {
- throw new Exception("Global AST transformation should only be applied once")
+ throw new Exception('Global AST transformation should only be applied once')
}
}
-
}
-
}