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.
Join the DZone community and get the full member experience.
Join For FreeWith 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.
Published at DZone with permission of Hubert Klein Ikkink, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments