Groovy has many features that make it great for writing DSLs (domain-specific languages):
Closures with delegates.
Parentheses and dots (.) are optional (command chains).
Ability to add methods to standard classes using Categories and Extension modules.
The ability to override many operators (plus, minus, etc.).
The methodMissing and propertyMissing methods.
Domain-specific languages can be useful for many purposes, such as allowing domain experts to read and write code or to clarify the meaning of business logic. They allow business experts to read or write code without having to be programming experts.
Closure with Delegate
Within Groovy you can take a block of code (a closure) as a parameter and then call it using a local variable as a
delegate. For example, imagine you have the following code for sending SMS texts:
1 class SMS {
2 String from, to, body;
3 def from(String fromNumber) {
4 from = fromNumber
5 }
6 def to(String toNumber) {
7 to = toNumber
8 }
9 def body(String body) {
10 this.body = body
11 }
12 def send() {
13 // send the text.
14 }
15 }
In Java, you’d need to use this the following way:
1 SMS m = new SMS();
2 m.from("555-432-1234");
3 m.to("555-678-4321");
4 m.body("Hey there!");
5 m.send();
In Groovy you can add the following static method to the
SMS class for DSL-like usage (
block is expected to be a closure):
1 def static send(@DelegatesTo(SMS) Closure block) {
2 SMS m = new SMS()
3 block.delegate = m
4 block()
5 m.send()
6 }
This sets the
SMS object as a delegate for the block so that methods are forwarded to it. The (optional)
@DelegatesTo(SMS) annotation
tells the compiler and IDE what class is used as delegate to the closure. Now you can do the following:
1 SMS.send {
2 from '555-432-1234'
3 to '555-678-4321'
4 body 'Hey there!'
5 }
This removes a lot of repetition from the code.
Command Chains
As noted earlier, Groovy has support for command chains which allow you to completely omit parenthesis and dots when making method calls with one or more parameters.
For example, let’s take the previous class for sending SMS messages but convert it to support the command chain
syntax.
1 class SMS {
2 String from, to, body;
3 SMS from(String fromNumber) {
4 from = fromNumber; return this
5 }
6 SMS to(String toNumber) {
7 to = toNumber; return this
8 }
9 SMS body(String body) {
10 this.body = body; return this
11 }
12 def send() { /* send the text */ }
13 def static send(@DelegatesTo(SMS) Closure block) {
14 /* same as before */ }
15 }
Now our DSL syntax can be
used like the following:
1 SMS.send {
3 from '555-432-1234' to '555-678-4321'
4 body 'Hey there!'
5 }
Overriding Operators
In Groovy you can
override operators simply by naming your methods using the English word for the operator. For example,
plus for
+ and
minus for
-. See the following table for more operators:
Operator | Method Name |
---|
+ | plus |
- | minus |
* | multiply |
/ | div |
% | mod |
** | power |
| | or |
& | and |
ˆ | xor |
<< | leftShift |
>> | rightShift |
++ | next |
-- | previous |
For more complex
operators, the variables “a”, “b”, and “c” are used to demonstrate how they are used in the following table (where “a” is an instance of the class defining the method):
Example | Method Declaration |
---|
a() | call() |
a as b | asType(b) |
a[b] | getAt(b) |
a[b] = c | putAt(b,c) |
+a | positive() |
-a | negative() |
~a | bitwiseNegate() |
b in a | isCase(b) |
For example, let’s create a class called
Logic with a Boolean value and define the
and and
or methods.
1 class Logic {
2 boolean value
3 Logic(v) {this.value = v}
4 def and(Logic other) {
5 this.value && other.value
6 }
7 def or(Logic other) {
8 this.value || other.value
9 }
10 }
Then, let’s use these methods and see if they work like we would expect:
1 def pale = new Logic(true)
2 def old = new Logic(false)
3
4 println "groovy truth: ${pale && old}" //true
5 println "using and: ${pale & old}" // false
6 println "using or: ${pale | old}" // true
Notice that using the built-in && operator
uses “Groovy truth” and returns true because both variables are non-null.
Next, let’s try defining the
leftShift and
minus operators on a class:
1 class Wizards {
2 def list = []
3 def leftShift(person) { list.add person }
4 def minus(person) { list.remove person }
5 String toString() { "Wizards: $list" }
6 }
7 def wiz = new Wizards()
8 wiz << 'Gandolf'
9 println wiz // Wizards: [Gandolf]
10 wiz << 'Harry'
11 println wiz // Wizards: [Gandolf, Harry]
12 wiz - 'Harry'
13 println wiz // Wizards: [Gandolf]
You can also implement the
putAt and
getAt methods that allow you to use the bracket syntax. For example:
1 def value = wiz[1] // uses getAt(1)
2 wiz[1] = value // uses putAt(1, value)
This can be useful when writing for a domain that uses the bracket notation.
Missing Methods and Properties
As noted previously, Groovy provides a way to implement functionality at runtime via the
methodMissing method
:
1 def methodMissing(String name, args)
However, Groovy also provides a way to intercept missing properties that are accessed using Groovy’s property syntax. Property access is implemented using propertyMissing(String name) (which returns a value) and property modification via propertyMissing(String name, Object value) (which sets the value for a property).
For example, here’s an excerpt from a DSL for chemical compounds:
1 class Chemistry {
2 public static void exec(Closure block) {
3 block.delegate = new Chemistry()
4 block()
5 }
6 def propertyMissing(String name) {
7 def comp = new Compound(name)
8 (comp.elements.size() == 1 && comp.elements.values()[0]==1) ?
9 comp.elements.keySet()[0] : comp
10 }
11 }
Given the following code for Compound and Element:
// Represents a chemical Element
class Element {
String symbol
Element(s) { symbol = s }
double getWeight() {symbol=='H' ? 1.00794 : 15.9994}
String toString() { symbol }
}
// Represents a chemical Compound
class Compound {
final Map elements = [:]
Compound(String str) {
def matcher = str =~ /([A-Z][a-z]∗)([0-9]+)?/
while (matcher.find()) add(
new Element(matcher.group(1)),
(matcher.group(2) ?: 1) as Integer)
}
void add(Element e, int num) {
if (elements[e]) elements[e] += num
else elements[e] = num
}
double getWeight() {
elements.keySet().inject(0d) { sum, key ->
sum + (key.weight * elements[key])
}
}
String toString() { "$elements" }
}
In this example,
propertyMissing (on lines 6-9) creates a new
Compound object and returns either the
Compound or an
Element object if there is only one element in the
Compound. This enables the creation of
Compounds based on the name of a
missing property
. For example:
1 def c = new Chemistry()
2 def water = c.H2O
3 println water // [H: 2, O: 1]
4 println water.weight // 18.01528
This is interpreted as trying to access a property named H2O, which triggers the propertyMissing method.
By using the static
exec method, this DSL reaches its full potential by exposing an instance of
Chemistry as a delegate to the closure, which allows for the following example:
1 Chemistry.exec {
2 def water = H2O
3 println water
4 println water.weight
5 }
This has the same effect of creating the H2O compound by calling the propertyMissing method
of Chemistry.
The full code for Groovy Chemisty is provided on GitHub. It provides the ability to compute the atomic weights of chemical compounds and percentages by atomic weight. It includes all known elements, their names, and atomic weights.
This DSL would be very difficult to implement without the help of Groovy. In Java, for example, you would need to use strings to represent compounds, polluting the syntax with tons of quotes and parentheses.
Extension Modules
Extension modules in Groovy allow you to add functionality to existing classes to a project by including a library. It works much like Categories but works everywhere without requiring a use clause.
To create an Extension module, create a file named
org.codehaus.groovy.runtime.ExtensionModule under the
META-INF/services/ directory. Within that file, list all of your
extensionClasses and
staticExtensionClasses. For example, create such a file with the following contents:
moduleName=adamldavis-groovy-dsl-example
moduleVersion=0.1-beta1
extensionClasses=com.adamldavis.gdsl.MyDSLExtension
staticExtensionClasses=com.adamldavis.gdsl.MyStaticDSLExtension
When included in a project, Groovy will look at all of the static methods of the MyDSLExtension class (in the com.adamldavis.gdsl package) and use them to add functionality to instances of the first parameter of each method’s class. The moduleName and moduleVersion properties are only there to make sure multiple conflicting versions of the library are not loaded.
For example, given the following code, the String class would essentially have the
upper() method added to it.
class MyDSLExtension {
static String upper(String it) { it.toUpperCase() }
}
Likewise, Groovy will look at all of the static methods of the
MyStaticDSLExtension class and use them to add functionality to the class of the first parameter of each method. For example, the following code would add a method,
boom, to the String class allowing
String.boom() to return “!”:
class MyStaticDSLExtension {
static String boom(String it) { "!" }
}
Note that for staticExtensionClasses, the given parameter (it in the preceding code) cannot be used within the method; it is only used for the type.