Creating DSLs in Java, Part 4: Where metaprogramming matters

Experience the power of dynamic methods with Scala and Groovy

1 2 3 4 5 Page 4
Page 4 of 5

Separating the processing code

First, we'll move the code to process the DSL into a separate class, as shown in Listing 5.

Listing 5. GameDSL

class GameDSL
{
  def playersAndScores = [:]

  def players(String[] names)
  {
    names.each { playersAndScores[it] = 0 }
  }

  def result()
  {
    def max = -1
    def winner = ''
    playersAndScores.each { name, score ->
      if (score > max)
      {
        max = score
        winner = name
      }
    }

    println "Winner is $winner with a score of $max"
  }

  def methodMissing(String name, args)
  {
    if (playersAndScores.containsKey(name) && args.length == 1 &&
args[0] instanceof Integer)
    {
      playersAndScores[name] = args[0]
    }
    else
    {
      throw new MissingMethodException(name, this.class, args)
    }
  }

  def static process(dsl)
  {
    def closure = (Closure) new GroovyShell().evaluate("{->" + dsl +
"}")
    closure.delegate = new GameDSL()
    closure()
  }
}

Most of the code is quite obvious. The only new method is process. It accepts the dsl as a String, forms a closure around it, and executes the closure in the context of an instance of GameDSL (set by the delegate property of closure).

Now, let's move the DSL into a separate file, game.dsl:

players 'James', 'John', 'Jake'
James 12
John 14
Jake 9
result()

To process this DSL, we need to send the content of game.dsl to the process method of GameDSL. Here's the code to do that:

GameDSL.process(new File('game.dsl').text)

Processing the DSL in Java

Processing the DSL was easy to do in Groovy, and it's just as easy to do in Java. Listing 6 shows how you can invoke the Groovy process method using Java code.

Listing 6. Processing the Groovy DSL in Java

import java.util.Scanner;
import java.io.File;

public class UseDSL
{
  public static void main(String[] args) throws Exception
  {
    String dsl = new Scanner(new File("game.dsl")).useDelimiter("\
\Z").next();
    GameDSL.process(dsl);
  }
}

To run this, type

java -classpath $GROOVYCLASSPATH:. UseDSL

where GROOVYCLASSPATH is an environment variable pointing to groovy-all-1.6-beta-1.jar (or to the appropriate version of Groovy you're using). Note: on Windows use %GROOVYCLASSPATH% instead of $GROOVYCLASSPATH.

Refine the DSL

We've roughed out our DSL and it's proven functional. Now we focus on refinement -- making sure that the DSL is as free of ceremony and as clear in essence as we can make it. First, we'll remove the parentheses from the last line of the DSL, so that it reads:

players 'James', 'John', 'Jake'
James 12
John 14
Jake 9
result

Go ahead and run the code and see what happens:

Caught: groovy.lang.MissingPropertyException: No such property: result
for class: ...

Uh oh, Groovy is complaining that result is not a valid property name. So, when you strip out the parentheses, it assumes that the given name is a property. In Groovy, the parentheses are almost optional. If a method takes parameters (like the players method), then you can lose the parentheses. However, if the method does not take any parameters, Groovy insists that you provide the parentheses.

You may not like this rule, but you don't need to fight with it. If Groovy is expecting a property, why not simply give it just that? Instead of writing a method named "result," write one named "getResult." Groovy will then apply the Java beans convention and recognize this as a result property. Listing 7 shows the modified version of the code.

Listing 7. Getting around Groovy

class GameDSL
{
  def playersAndScores = [:]

  def players(String[] names)
  {
    names.each { playersAndScores[it] = 0 }
  }

  def getResult()
  {
    def max = -1
    def winner = ''
    playersAndScores.each { name, score ->
      if (score > max)
      {
        max = score
        winner = name
      }
    }

    println "Winner is $winner with a score of $max"
  }

  def methodMissing(String name, args)
  {
    if (playersAndScores.containsKey(name) && args.length == 1 &&  
args[0] instanceof Integer)
    {
      playersAndScores[name] = args[0]
    }
    else
    {
      throw new MissingMethodException(name, this.class, args)
    }
  }

  def static process(dsl)
  {
    def closure = (Closure) new GroovyShell().evaluate("{->" + dsl +  
"}")
    closure.delegate = new GameDSL()
    closure()
  }
}

Is it loud in here?

Sometimes quotes are just so much noise in your code, and that is definitely the case in a DSL. Our next step is to get rid of the remaining static in our code, but cutting out the single quotes around player names. Here's the result:

players James, John, Jake
James 12
John 14
Jake 9
result

If you run it, however, you will get the following error:

Caught: groovy.lang.MissingPropertyException: No such property: James  
for class: ...

Groovy is complaining that these names without quotes are not valid property names. When we presented 'James' in single quotes (or double quotes), Groovy took it the name as a String parameter. Without the quotes, Groovy thinks we're referring to a property.

We can get around this by providing a propertyMissing method, just like the methodMissing method we used for dynamic methods. Here's the refined code:

def propertyMissing(String name) { name }

If you re-run the code with the above method added to the GameDSL class, you will get the following result:

Winner is John with a score of 14

Ah, the sweet sound of success!

1 2 3 4 5 Page 4
Page 4 of 5