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 17:15:36 UTC

[groovy] branch GROOVY_3_0_X 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 GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
     new 2a641e4950 GROOVY-10654: `ASTTransformationCustomizer`: enum and array parameters
2a641e4950 is described below

commit 2a641e49509f7becbe081f7c27eb8b74b9377530
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Jun 10 10:25:33 2022 -0500

    GROOVY-10654: `ASTTransformationCustomizer`: enum and array parameters
    
    3_0_X backport
---
 .../customizers/ASTTransformationCustomizer.groovy | 104 +++++++------
 .../ASTTransformationCustomizerTest.groovy         | 170 ++++++++++++---------
 2 files changed, 153 insertions(+), 121 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 d6ac4aaccf..40591a3bae 100644
--- a/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy
+++ b/src/main/groovy/org/codehaus/groovy/control/customizers/ASTTransformationCustomizer.groovy
@@ -18,14 +18,13 @@
  */
 package org.codehaus.groovy.control.customizers
 
+import groovy.transform.AutoFinal
 import groovy.transform.CompilationUnitAware
 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
@@ -38,6 +37,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,12 +83,13 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.constX
  * @since 1.8.0
  * 
  */
+@AutoFinal
 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
@@ -98,9 +100,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))
     }
@@ -112,7 +114,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)
     }
 
@@ -126,15 +128,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)
     }
 
@@ -144,9 +146,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))
     }
@@ -155,14 +157,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
@@ -175,19 +177,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
     }
@@ -198,7 +200,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()
@@ -216,8 +218,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()
@@ -236,57 +238,59 @@ 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')
     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) {
             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 b861ed8722..2257088242 100644
--- a/src/test/org/codehaus/groovy/control/customizers/ASTTransformationCustomizerTest.groovy
+++ b/src/test/org/codehaus/groovy/control/customizers/ASTTransformationCustomizerTest.groovy
@@ -18,138 +18,166 @@
  */
 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
 import org.codehaus.groovy.ast.ClassHelper
-import org.codehaus.groovy.ast.expr.ClassExpression
-import org.codehaus.groovy.ast.expr.PropertyExpression
 import org.codehaus.groovy.control.CompilePhase
 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
+final class ASTTransformationCustomizerTest {
 
-    void setUp() {
-        configuration = new CompilerConfiguration()
-    }
+    private final CompilerConfiguration config = new CompilerConfiguration()
+    private final GroovyShell shell = new GroovyShell(config)
 
+    @Test
     void testLocalTransformation() {
-        customizer = new ASTTransformationCustomizer(Log)
-        configuration.addCompilationCustomizers(customizer)
-        def shell = new GroovyShell(configuration)
-        def result = shell.evaluate("""
+        def customizer = new ASTTransformationCustomizer(Log)
+        config.addCompilationCustomizers(customizer)
+        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)
+    @Test
+    void testLocalTransformationStringParameter() {
+        def customizer = new ASTTransformationCustomizer(Log)
         customizer.annotationParameters = [value: 'logger']
-        configuration.addCompilationCustomizers(customizer)
-        def shell = new GroovyShell(configuration)
-        def result = shell.evaluate("""
+        config.addCompilationCustomizers(customizer)
+        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 customizer = new ASTTransformationCustomizer(Newify)
+        customizer.annotationParameters = [value: [Integer, Long]]
+        config.addCompilationCustomizers(customizer)
+        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 customizer = new ASTTransformationCustomizer(Newify)
+        customizer.annotationParameters = [value: new Class[]{Integer, Long}]
+        config.addCompilationCustomizers(customizer)
         def result = shell.evaluate '''
             Integer(11) + Long(31)
         '''
         assert result == 42
     }
 
-    void testAnyExpressionAsParameterValue() {
-        customizer = new ASTTransformationCustomizer(value:100, unit: new PropertyExpression(new ClassExpression(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 customizer = new ASTTransformationCustomizer(TimedInterrupt)
+        customizer.annotationParameters = [value: 300, unit: propX(classX(ClassHelper.make(TimeUnit)),'MILLISECONDS')]
+        config.addCompilationCustomizers(customizer)
+        assert shell.evaluate('''import java.util.concurrent.TimeoutException
+            boolean interrupted = false
+            try {
+                10.times {
+                    sleep 100
+                }
+            } catch (TimeoutException ignore) {
+                interrupted = true
+            }
+            interrupted
+        ''')
+    }
 
-boolean interrupted = false
-try {
-    100.times {
-        Thread.sleep(100)
+    @Test // GROOVY-10654
+    void testLocalTransformationEnumerationConstantParameter() {
+        def customizer = new ASTTransformationCustomizer(TimedInterrupt)
+        customizer.annotationParameters = [value: 300, unit: TimeUnit.MILLISECONDS]
+        config.addCompilationCustomizers(customizer)
+        assert shell.evaluate('''import java.util.concurrent.TimeoutException
+            boolean interrupted = false
+            try {
+                10.times {
+                    sleep 100
+                }
+            } catch (TimeoutException ignore) {
+                interrupted = true
+            }
+            interrupted
+        ''')
+    }
+
+    //--------------------------------------------------------------------------
+
+    @Test
+    void testGlobalTransformation() {
+        TestTransformation transformation = new TestTransformation()
+        config.addCompilationCustomizers(new ASTTransformationCustomizer(transformation))
+        assert shell.evaluate('true')
+        assert transformation.applied
     }
-} catch (TimeoutException e) {
-    interrupted = true
-}
 
-interrupted'''
-        assert result
+    @Test
+    void testGlobalTransformation2() {
+        TestTransformation transformation = new TestTransformation()
+        config.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')
             }
         }
-        
     }
-
 }