Thursday, March 26, 2009

Groovy 1.6 AST Transformation Example

With Groovy 1.6 released and this article on InfoQ got me to try some new features.

AST Transformation
With Groovy 1.6 you can define local and global transformation using annotations.
So let's define @AssertParamsNotNull annotation for method which would perform AST Transformation at compile time. It simply asserts parameters of method are not null. I am following groovy documentation here and blog entry here by Hamlet D'Arcy.

There are three components to defining local AST Transformation

Step 1: Define Annotation
Step 2: Define GroovyASTTransformation
Step 3: Test and Usage

Pre-requisites
Download Groovy 1.6
Download Groovy Eclipse plugin for 1.6, available from this update URL.
 http://dist.groovy.codehaus.org/distributions/updateDev_1.6/
This url is different from their usual update url. I believe they will update their distributions update url (http://dist.codehaus.org/groovy/distributions/update/)

Step 1: Define Annotation @AssertParamsNotNull

package com.learn.groovy16.ast.local
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import java.lang.annotation.ElementType
import org.codehaus.groovy.transform.GroovyASTTransformationClass
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass(["com.learn.groovy16.ast.local.AssertParamsNotNullASTTransformation"])
public @interface AssertParamsNotNull{
One thing to notice is here defining the ASTTransformation class. It requires complete qualified class name. Rest of the things are standard annotation declaration.

While working on getting this running in my environment I faced following errors/issues.

Unknown Type: ANNOTATION_DEF at line ...
This error comes if for some reason eclipse environment is still using older groovy installation. Check your GROOVY_HOME points to Groovy 1.6. If you are able to run this through command line, but eclipse is giving your errors. Update your Groovy Eclipse plugin from dev update URL. You may have to wipe it clean for eclipse to understand it.

Expected '{' but was found...
This error comes if for some reason there funny newline characters. I think I got this error if right after "public @interface AssertParamsNotNull" if { is in the next line. I believe I got funny character in between those two. I changed the text file encoding to UTF-8 and the error went away. It allowed me to have braces on the next line.

Step 2: Define GroovyASTTransformation


package com.learn.groovy16.ast.local
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.Parameter
import org.codehaus.groovy.ast.stmt.Statement
import org.codehaus.groovy.ast.stmt.AssertStatement
import org.codehaus.groovy.ast.expr.BooleanExpression
import org.codehaus.groovy.ast.expr.NotExpression
import org.codehaus.groovy.ast.expr.VariableExpression
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
public class AssertParamsNotNullASTTransformation implements ASTTransformation
{
public void visit(ASTNode[] nodes, SourceUnit source)
{
List methods = source.getAST()?.getMethods()
methods.findAll{MethodNode method ->
method.getAnnotations(new ClassNode(AssertParamsNotNull))
}.each{MethodNode method ->
List existingStatements = method.getCode().getStatements()
Parameter[] parameters = method.getParameters()
parameters.eachWithIndex(){ parameter, i ->
existingStatements.add(i, createAssertStatement(parameter))
}
}
}

public Statement createAssertStatement(Parameter parameter){
return new AssertStatement(
new BooleanExpression(
new VariableExpression(parameter))
}
}

This is where most of the trick is happening. This class implements ASTTransformation interface implementing visit(ASTNode[], SourceUnit source) method. This method gets all methods marked with AssertParamsNotNull annotation, iterates over all method, iterates over all method parameters and calls createAssertStatement(parameter). createAssertStatement(parameter) creates Statements equivalent to assert parameter and inserts it into the AST tree. All this happens at compile time. So notice line
@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
Creating Statement and Expression using API is really cumbersome. It is good they are working on builder support for the same.
 
Step 3: Test and Run


package com.learn.groovy16.ast.local

@com.learn.groovy16.ast.local.AssertParamsNotNull

def foo(String var)
{
println var
}
foo("Hello")
foo(null)


Note the annotation with fully qualified name.

Interesting thing is that all transformation happens at compile time. It does not stop

Other interesting AST Transformations...

@CatchExceptions(list=[exception1, exception2, ..],rethrow=AppException)
Define AST transformation to remove clutterred exception handling logic and rethrow ApplicationException as noted by parameters of annotation. I think the challenge here will be to build AST Statement and Expression.

@SecureAccess(Role=Manager)
Allows to define security over method execution.

@Trace(isTraceEnabled=$GlobalTraceParameter)
Emits out trace of method. Entered, parameter values, exit, return type. This only gets emitted if GlobalTraceParameter is set to Y

Wednesday, March 11, 2009

Grails, Flex and MyEclipse

This is a small exercise that I undertook following documentation I found on grails website. Let's create small calculator application which adds two operands. The idea is to setup the flex plugin and see how it works.

I used following tools
  • MyEclipse with flex builder
  • Grails
  • Groovy Eclipse plugin
  • Grails Fex Plugin
Step 1: Install Groovy Eclipse plugin
MyEclipse 6.5 installation that I had did not came with groovy/grails plugin. One of the nice thing about MyEclipse is that it is eclipse with additional tools. Use the following plugin update URL to get groovy plugin.

Here is eclipse software update URL: http://dist.codehaus.org/groovy/distributions/update/.

This plugin is optional. You can use grails command line commands and that will work as well.

Step 2: Create Grails Project and Install Grails Flex Plugin
Next thing is to install Grails Flex plugin. This is still under development. The one I used is version 0.2.

Create Grails application using New --> Other... --> New Grails Project. This will create grails project.

Alternatively you an use grails command line to create app. Go to command line, change directory to project root folder in your workspace and type command:
              grails install-plugin flex.

This will install flex plugin for grails.

I was using MyEclipse with Flex Builder, it was easy to add flex nature to my project. To do that right click and add flex nature

Step 3: Create Calc UI
Now project is setup, we are ready to create calc.mxml. I created this under webapp folder so that it is visible.

Use following calc.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:RemoteObject id="ro" destination="calcService"/>
    <mx:Model id="myCalcData">
        <calc>
            <operand1>{number1.text}</operand1>
            <operand2>{number2.text}</operand2>
        </calc>
    </mx:Model>
    <mx:Panel>
        <mx:Form>
            <mx:FormItem label="Operand 1:">
                <mx:TextInput id="number1"/>
            </mx:FormItem>
            <mx:FormItem label="Operand 2:">
                <mx:TextInput id="number2"/>
            </mx:FormItem>
            <mx:Button label="Calculate" click="ro.calc(myCalcData.operand1,myCalcData.operand2)"/>
             <mx:Label text="Result: {ro.calc.lastResult} "  fontWeight="bold"/>
        </mx:Form>   
    </mx:Panel>       
</mx:Application>


Step 4: Create Grails HelloWorldService
Create grails new service from IDE. You can also create service by typing command
grails create-service HelloService

public class CalcService
{
  static expose = ['flex-remoting']
  def calc(int number1, int number2) { return number1 + number2 }
}

Step 5: Update configuration
Next step is to update configuration file, Config.groovy to enables web tier compiler for flex. Again, I am using similar file as in the example provided on grails website.

// enables the webtier compiler for all environments

Step 6: Run using command line

We are almost done, type in command line at project root to run the projectgrails run-app

Type in browser: http://localhost:8080/MyGrails/calc.mxml


You may need to change port, application context if you are using different configuration.