Loading...

Friday, June 3, 2011

Groovy Goodness: Add AST Transformations Transparently to Scripts

With Groovy 1.8 we can add compilation customizers when for example we want to run a Groovy script from our application code. Cedric Champeau has a nice blog post about this feature. And we already learned about the import customizer in a previous Groovy Goodness blog post.

Another customizer is the ASTTransformationCustomizer. We can use this customizer to add an AST transformation to a script. The AST transformation is then applied to all classes in the script. This means we can only apply transformations that can be used at class level. Another thing we need to notice is that we cannot set parameters for the AST transformation, so only the defaults for the parameters are used.

We can use both local and global AST transformations with the ASTTransformationCustomizer. For a local transformation we use the annotation class, for a global transformation we pass the AST transformation class to the constructor.

package com.mrhaki.blog

import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.control.CompilerConfiguration
import groovy.transform.Canonical

def conf = new CompilerConfiguration()
conf.addCompilationCustomizers(new ASTTransformationCustomizer(Canonical))

def shell = new GroovyShell(conf)
shell.evaluate '''
package com.mrhaki.blog

class User {
    String username, fullname
}

// TupleConstructor is added by Canonical transformation.
def user = new User('mrhaki', 'Hubert A. Klein Ikkink')
// ToString is added by Canonical transformation.
assert user.toString() == 'com.mrhaki.blog.User(mrhaki, Hubert A. Klein Ikkink)'

// AST transformation is also applied to the Script class.
assert this.toString() == 'com.mrhaki.blog.Script1()'
'''

Wednesday, June 1, 2011

Groovy Goodness: Add Imports Transparently to Scripts with ImportCustomizer

Since Groovy 1.8.0 we can easily setup import statements for a Groovy compilation unit (for example a Groovy script file) without adding the imports to the script source. For example we have created domain classes in our own package. The script authors shouldn't know about the packages and just pretend the classes are accessible from the 'default' package.

We define an ImportCustomizer and use the addImport methods to add packages, classes, aliases to our script. The configured ImportCustomizer is added to a CompilerConfiguration object. We will see in future blog posts how we can even add more customizers to the CompilerConfiguration. We use the configuration when we create a GroovyShell and the information is applied to scripts we run with the created shell.

First we create a class and enum in the com.mrhaki.blog package. We compile the code so we have class files we can add to a classpath of another application.

// File: Post.groovy
// Compile with groovyc Post.groovy
package com.mrhaki.blog

class Post {
    String title
    Type type = Type.BLOG
}

enum Type {
    BLOG, NEWS
}

Next we create a Groovy script that will execute the following code:

// File: sample.groovy
// Article is imported as alias for com.mrhaki.blog.Post
def article = new Article(title: 'Groovy Goodness')

assert article.title == 'Groovy Goodness'
// BLOG is statically imported from com.mrhaki.blog.Type.*
assert article.type == BLOG
assert article.class.name == 'com.mrhaki.blog.Post'

Now we are ready to create a small application that will execute sample.groovy. We must add the com.mrhaki.blog.Post and com.mrhaki.blog.Type compiled classes to the classpath if we run the following script:

// File: RunScript.groovy
// Run with groovy -cp . RunScript
import org.codehaus.groovy.control.customizers.ImportCustomizer
import org.codehaus.groovy.control.CompilerConfiguration

// Add imports for script.
def importCustomizer = new ImportCustomizer()
// import static com.mrhaki.blog.Type.*
importCustomizer.addStaticStars 'com.mrhaki.blog.Type'
// import com.mrhaki.blog.Post as Article
importCustomizer.addImport 'Article', 'com.mrhaki.blog.Post'

def configuration = new CompilerConfiguration()
configuration.addCompilationCustomizers(importCustomizer)

// Create shell and execute script.
def shell = new GroovyShell(configuration)
shell.evaluate new File('sample.groovy')