In the previous blog example we created DSL using ExpandoMetaClass. Users will define the script starting with node jmx { }. With the use of ExpandoMetaClass we added the dynamic method which in turn delegated to class JmxClosureDelegate. Here is the snippet of the code.
What we essentially did here:
static void runEngine(File dsl){
Script dslScript = new GroovyShell().parse(dsl.text)
dslScript.metaClass = createExpandoMetaClass(dslScript.class, {
ExpandoMetaClass emc ->
emc.jmx = {
Closure cl ->
cl.delegate = new JmxClosureDelegate()
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
}
})
dslScript.run()
}
- Using GroovyShell it parses the script file passed as input.
- Defines the ExpandoMetaClass and adds method by name "jmx" having closure as parameter.
Getting rid of the Commas
The problem with creating methods with two arguments is that you have to specify commas between them. For example,
Undoubtedly commas clutter the langauge grammer. To get rid of the commas we used trick provided on Groovy users list.
server "nameofserver", {
...
}
Using AstTransformation
//To avoid using "," between String and Closure argument
def methodMissing(String name, args) {
return [name, args[0]]
}
Now lets explore a different possibility. You can achieve similar result using AstTransformation at compile time. The goal remains the same and that is to add method with name "jmx" with closure parameter.
First we will define the annotation.
Transformation Class
//import statements skipped for brevity
@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.METHOD])
@GroovyASTTransformationClass(["info.kartikshah.jmx.ast.JmxDslTransformation"])
public @interface UseJmxDsl {}
This transformation class needs to perform two activities:
- Add method jmx(Closure cl) method
- Invoke the script method being defined
We will use AstBuilder's buildFromSpec option to generate it. (AstBuilders added with Groovy 1.7 definitely makes generating statement structure relatively easy and clutter free. Not to mention it is also an example of DSL added to the groovy language :-) )
jmx = {
Closure cl ->
cl.delegate = new JmxClosureDelegate()
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
}
Why use AstTransformation?
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
class JmxDslTransformation implements ASTTransformation {
static int PUBLIC = 1
static int STATIC = 8
void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
//Add method jmx(Closure cl)
ClassNode declaringClass = astNodes[1].declaringClass
MethodNode jmxMethod = makeMethod()
declaringClass.addMethod(jmxMethod)
//Insert method call inside run method of the script class
MethodNode annotatedMethod = astNodes[1]
List<MethodNode> allMethods = sourceUnit.AST?.classes*.methods.flatten()
MethodNode runMethod = allMethods.find{ MethodNode method ->
method.name == "run"
}
List existingStatements = runMethod.getCode().getStatements()
existingStatements.add(0, createMethodCall(annotatedMethod))
}
Statement createMethodCall(MethodNode methodNode){
def statementAst = new AstBuilder().buildFromSpec {
expression{
methodCall {
variable "this"
constant methodNode.name
argumentList {}
}
}
}
Statement stmt = statementAst[0]
stmt
}
MethodNode makeMethod() {
def ast = new AstBuilder().buildFromSpec {
method('jmx', PUBLIC | STATIC, Void.TYPE) {
parameters {
parameter 'cl': Closure.class
}
exceptions {}
block {
expression {
binary {
property {
variable "cl"
constant "delegate"
}
token "="
constructorCall(JmxClosureDelegate.class) {
argumentList()
}
}
}
expression {
binary {
property {
variable "cl"
constant "resolveStrategy"
}
token "="
property {
classExpression Closure
constant "DELEGATE_FIRST"
}
}
}
expression {
methodCall {
variable "cl"
constant "call"
argumentList {}
}
}
}
}
}
MethodNode jmxMethod = ast[0]
jmxMethod
}
}
The question is why one would want to use AstTransformation when you can add method during runtime. For the given scenario, it is correct that you want to stick with adding method runtime. But consider scenario where you want to "redefine" meaning of Groovy's syntax. For example like following imaginary script using Statement Labels to add more readability to your DSL syntax.
Spock Framework does similar twist by redefining meaning of existing construct.
@info.kartikshah.jmx.ast.UseJmxDsl
runDsl () {
jmx {
setup:
server "service:jmx:rmi://localhost/jndi/rmi://localhost:1090/jmxconnector"
query "jboss.web:*"
findAll "j2eeType=Servlet"
draw:
chart {
chartType="Bar"
attributes={m-> [m.loadTime, m.objectName.find("name=([^,]*)"){it[1]}]}
labels=["Load Time per Servlet", "Servlet", "Time"]
options=[false, true, true]
windowTitle="JBoss Servlet Processing Time"
width=1200
height=700
orientation="HORIZONTAL"
refreshRate=5000
show()
}
}
}
With this type of language structure you will end up defining your own set of keywords, supporting parser and few AstTransformation to change the meaning of existing Groovy Syntax.
Blogged with the Flock Browser
No comments:
Post a Comment