Writing Your First DSL using Groovy
In this example, we will be writing a DSL to configure the valid values for the columns of a given table.
Below is how we write the DSL:
Sample.groovy:
[java]
package com.hashfold.groovy.dsl
import com.hashfold.groovy.dsl.Schema
Schema.create("MyTable") {
column1 1,2,3,4,5,6,7,8,9,10
column2 50,80,{println 90+1 }
column3 "fixed", { param -> println "envData=${envData}-param="+param }
}
[/java]
Schema.groovy:
[java]
package com.hashfold.groovy.dsl
import groovy.xml.MarkupBuilder
/**
* Processes a simple DSL to create various formats of a memo: xml, html, and text
*/
class Schema {
String name
//def columns = []
def cols = [:]
/**
* This method accepts a closure which is essentially the DSL. Delegate the closure methods to
* the DSL class so the calls can be processed
*/
def static Object create(String dataName, closure) {
Schema dataDsl = new Schema()
dataDsl.name = dataName
//println dataDsl.name
// any method called in closure will be delegated to the memoDsl class
closure.delegate = dataDsl
closure()
//test closure will be invoked from Java!
//dataDsl.cols["testClosure"] = [{ param -> println "print my closure - "+param }]
return dataDsl.cols
}
def storage = [:]
def propertyMissing(String name, value) {
println "Undefined Property setter: ${name} = ${value}"
storage[name] = value
}
def propertyMissing(String name) {
println "Undefined Property getter: ${name}"
storage[name]
}
/*
* This method will be called for each column names e.g. column1…
*/
def methodMissing(String methodName, args) {
List data = []
args.each {
if(it instanceof Closure) {
/*
* lets not evaluate the 'validate' closure.
* we leave it to evaluate on Java side against
* actual data. in case the evaluation returns 0/False,
* we throw validation error
*/
if(methodName.toLowerCase() == "validate") {
data << it
}
else {
//lets not evaluate the closure here. leave it for java
//data << it()
data << it
}
}
else {
data << it } } //this is the last statement of the method which will be returned from the Groovy to Java cols[methodName] = data } //This is the test method which executes the given closure def static test(String name, closure) { println name Schema dataDsl = new Schema() closure.delegate = dataDsl closure() } def getDump() { println ">>>Dumping..."
println name
cols.each {
println "name:"+it.name
println "obj:"+it.range
println "\t"+it.range
it.range.each {
println "\t\t"+it
if(it instanceof Closure) {
it()
}
}
}
}
}
[/java]
Here is how we call the above Groovy script from Java and execute it:
GroovyCaller.java:
[java]
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import java.io.File;
public class GroovyCaller {
public static void main(String[] args) throws Exception {
Binding binding = new Binding();
//Lets pass some data from Java to Groovy using Binding
binding.setVariable("envData", new Integer(2));
GroovyShell shell = new GroovyShell(binding);
File file = new File("Sample.groovy");
Object value = shell.evaluate(file);
//Lets print the data returned by Groovy (from last method call of MethodMissing)
System.out.println(value);
/* lets invoke the Closure if any! Note here that
* any column value written inside brackets ‘{}’ will be treated
* as Groovy Closure which are execution blocks
*/
LinkedHashMap<String, ArrayList<Object>> cols = (LinkedHashMap<String, ArrayList<Object>>) value;
for (String variable : cols.keySet()) {
ArrayList<Object> list = cols.get(variable);
for(Object data: list) {
if(data instanceof Closure) {
Closure c = (Closure) data;
c.call("MyTest");
}
}
}
}
}
[/java]
Output from GroovyCaller.java:
{column1=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
column2=[50, 80, com.hashfold.groovy.dsl.Sample$_run_closure1_closure2@37963796],
column3=[fixed, com.hashfold.groovy.dsl.Sample$_run_closure1_closure3@1d001d0],
91
envData=3 - param=MyTest
The above last two lines are the result of the Closure execution from Java.
In order to build the java code, you need to add below plugin to your maven pom.xml file:
[xml]
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.2</version>
<configuration>
<providerSelection>1.7</providerSelection>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-1.7</artifactId>
<version>1.2</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
[/xml]
Also add below dependencies:
[xml]
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
</dependency>
</dependencies>
[/xml]
Groovy DSL Reference Book: Groovy for Domain-Specific Languages