Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Groovy Goodness — Customizing The Groovy Compiler

DZone's Guide to

Groovy Goodness — Customizing The Groovy Compiler

With Groovy we can configure the compiler for our source files just like we can configure the Groovy compilation unit when we use GroovyShell to execute scripts. Read on to learn more.

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

With Groovy we can configure the compiler for our source files just like we can configure the Groovy compilation unit when we use GroovyShell to execute scripts. For example, we can add annotations to source files before they are compiled without adding them to the actual source code ourselves. Suppose we want to apply the TypeChecked or CompileStatic AST transformation annotation to all source files in our project. We only have to write a configuration file and specify the name of the configuration file with the --configscript option of the Groovy compiler. If we use Gradle to build our Groovy project, we can also customize the GroovyCompile task to set the configuration file.

The configuration file has an implicit object with the name configuration of type CompilerConfiguration. Also, there is a builder syntax available via the CompilerCustomizationBuilder class. Let's look at both ways to define our custom configuration. We want to add theCompileStatic annotation to all classes, together with the ToString AST transformation annotation. Next, we also want to add the packagejava.time as implicit import for our source files. This means we don't have to write an import statement in our code to include classes from this package. Finally, we add an ExpressionChecker that will fail the compilation of our project if a variable name is only 1 character. We assume we use Gradle to build our project and we place the file groovycConfig.groovy in the directory src/groovyCompile. We must not name the fileconfiguration.groovy, because there is already a variable with the name configuration in the script and this will confuse the compiler.

// File: src/groovyCompile/groovycConfig.groovy
import groovy.transform.CompileStatic
import groovy.transform.ToString
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.control.customizers.SecureASTCustomizer

// Add the AST annotation @CompileStatic
// and @ToString to all classes.
configuration.addCompilationCustomizers(
        new ASTTransformationCustomizer(CompileStatic))
configuration.addCompilationCustomizers(
        new ASTTransformationCustomizer(ToString))

// Add implicit import for all classes
// for the package java.time.
def imports = new ImportCustomizer()
imports.addStarImports('java.time')
configuration.addCompilationCustomizers(imports)

// Define expression checker to deny 
// usage of variable names with length of 1.
def smallVariableNames = { expr ->
    if (expr instanceof VariableExpression) {
        expr.variable.size() > 1
    } else {
        true
    }
} as ExpressionChecker

def secureAstCustomizer = new SecureASTCustomizer()
secureAstCustomizer.addExpressionCheckers(smallVariableNames)
configuration.addCompilationCustomizers(secureAstCustomizer)

With the builder syntax of CompilerCustomizatioBuilder we also have the flexibility to pass parameters to the AST transformations we want to apply. We configure the ToString annotation to include the names of the properties in the generated toString method implementation:

// File: src/groovyCompile/groovycConfig.groovy
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codehaus.groovy.control.customizers.SecureASTCustomizer.ExpressionChecker

// Using CompilerCustomizationBuilder.withConfig 
// method, where the class
// CompilerCustomizationBuilder is implicitly 
// imported for this script.
withConfig(configuration)  {

    ast(groovy.transform.CompileStatic)

    // Define includeNames parameter for ToString
    // AST annotation.
    ast(includeNames: true, groovy.transform.ToString)

    imports {
        star('java.time')
    }

    // Define expression checker to deny 
    // usage of variable names with length of 1.
    def smallVariableNames = { expr ->
        if (expr instanceof VariableExpression) {
            expr.variable.size() > 1
        } else {
            true
        }
    } as ExpressionChecker
    secureAst {
        addExpressionCheckers smallVariableNames
    }
}

Next, we configure the GroovyCompile task compileGroovy in our Gradle project to use our configuration file when the code is compiled. We do this via the property groovyOptions of the compile task:

// File: build.gradle
plugins {
    id "groovy"
}

repositories {
    jcenter()
}

dependencies {
    compile "org.codehaus.groovy:groovy-all:2.4.5"
    testCompile "org.spockframework:spock-core:1.0-groovy-2.4"
}

// Add the configuration script file
// to the compiler options.
compileGroovy.groovyOptions.configurationScript = file('src/groovyCompile/groovycConfig.groovy')

Written with Groovy 2.4.5.

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
groovy ,compiler

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}