alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Groovy example source code file (ASTTransformationCustomizer.groovy)

This example Groovy source code file (ASTTransformationCustomizer.groovy) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Groovy tags/keywords

annotation, annotation, ast, ast, asttransformation, asttransformationcustomizer, asttransformationcustomizer, class, class, closureexpression, compilephase, illegalargumentexception, illegalargumentexception, map, provided

The Groovy ASTTransformationCustomizer.groovy source code

/*
 * Copyright 2003-2011 the original author or authors.
 *
 * Licensed 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 org.codehaus.groovy.control.customizers

import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.classgen.GeneratorContext
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.Annotation
import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.Parameter

/**
 * This customizer allows applying an AST transformation to a source unit with
 * several strategies.
 *
 * Creating a customizer with the {@link ASTTransformationCustomizer#ASTTransformationCustomizer(Class)
 * class constructor} will trigger an AST transformation for
 * each class node of a source unit. However, you cannot pass parameters to the annotation so the default values
 * will be used. Writing :
 * <pre>
 *     def configuration = new CompilerConfiguration()
 *     configuration.addCompilationCustomizers(new ASTTransformationCustomizer(Log))
 *     def shell = new GroovyShell(configuration)
 *     shell.evaluate("""
 *        class MyClass {
 *
 *        }""")
 * </pre>
 *
 * is equivalent to :
 *
 * <pre>
 *     def shell = new GroovyShell()
 *     shell.evaluate("""
 *        @Log
 *        class MyClass {
 *
 *        }""")
 * </pre>
 *
 * The class passed as a constructor parameter must be an AST transformation annotation.
 *
 * Alternatively, you can apply a global AST transformation by calling the
 * {@link ASTTransformationCustomizer#ASTTransformationCustomizer(ASTTransformation) AST transformation
 * constructor}. In that case, the transformation is applied once for the whole source unit.
 *
 * Unlike a global AST transformation declared in the META-INF/services/org.codehaus.groovy.transform.ASTTransformation
 * file, which are applied if the file is in the classpath, using this customizer you'll have the choice to apply
 * your transformation selectively. It can also be useful to debug global AST transformations without having to
 * package your annotation in a jar file.
 *
 * @author Cedric Champeau
 *
 * @since 1.8.0
 * 
 */
class ASTTransformationCustomizer extends CompilationCustomizer {
    private final AnnotationNode annotationNode;
    private final ASTTransformation transformation
    private boolean applied = false; // used for global AST transformations

    ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation) {
        super(findPhase(transformationAnnotation))
        final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation)
        this.transformation = clazz.newInstance()
        this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation))
    }

    ASTTransformationCustomizer(final ASTTransformation transformation) {
        super(findPhase(transformation))
        this.transformation = transformation
        this.annotationNode = null
    }

    ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation) {
        super(findPhase(transformationAnnotation))
        final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation)
        this.transformation = clazz.newInstance()
        this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation))
        setAnnotationParameters(annotationParams)
    }

    ASTTransformationCustomizer(final Map annotationParams, final ASTTransformation transformation) {
        this(transformation)
        setAnnotationParameters(annotationParams)
    }


    private static Class<ASTTransformation> findASTTranformationClass(Class anAnnotationClass) {
        final GroovyASTTransformationClass annotation = anAnnotationClass.getAnnotation(GroovyASTTransformationClass)
        if (annotation==null) throw new IllegalArgumentException("Provided class doesn't look like an AST @interface")

        Class[] classes = annotation.classes()
        String[] classesAsStrings = annotation.value()
        if (classes.length+classesAsStrings.length>1) {
            throw new IllegalArgumentException("AST transformation customizer doesn't support AST transforms with multiple classes")
        }
        return classes?classes[0]:Class.forName(classesAsStrings[0])
    }

    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)
        if (annotation==null) throw new IllegalArgumentException("Provided ast transformation is not annotated with "+GroovyASTTransformation.name)

        annotation.phase()
    }

    private static CompilePhase findPhase(Class<? extends Annotation> annotationClass) {
        Class<ASTTransformation> clazz = findASTTranformationClass(annotationClass);

        findPhase(clazz.newInstance())
    }

    /**
     * Specify annotation parameters. For example, if the annotation is :
     * <pre>@Log(value='logger')
* You could create an AST transformation customizer and specify the "value" parameter thanks to this method: * <pre>annotationParameters = [value: 'logger'] * * 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 : * <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> * * @param params the annotation parameters * * @since 1.8.1 */ public 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 (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 ClosureExpression) { // avoid NPEs due to missing source code value.setLineNumber(0) value.setLastLineNumber(0) annotationNode.addMember(key, value) } else if (value instanceof Class) { annotationNode.addMember(key, new ClassExpression(ClassHelper.make(value))) } else { annotationNode.addMember(key, new ConstantExpression(value)) } } } @Override void call(SourceUnit source, GeneratorContext context, ClassNode classNode) { if (annotationNode!=null) { // this is a local ast transformation which is applied on every class node annotationNode.sourcePosition = classNode transformation.visit([annotationNode, classNode] as ASTNode[], source) } else { // this is a global AST transformation if (!applied) transformation.visit(null, source) } applied = true } }

Other Groovy examples (source code examples)

Here is a short list of links related to this Groovy ASTTransformationCustomizer.groovy source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.