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 3
Page 3 of 5

The Groovy-based internal DSL

In the next sections, we'll incrementally write an internal DSL based on a simple input file. When processed, the final DSL will provide the scores of our three players and tell us who won. If the first iteration of our DSL below looks familiar, it's because it also served as the basis of the external DSL in Creating DSLs in Java, Part 3.

Listing 2. The initial internal DSL

//version 1 of our DSL input file
players 'James', 'John', 'Jake'
James 12
John 14
Jake 9
result()

What you see in Listing 2 is a DSL made of pure, executable Groovy code. It currently looks more like a data input file than code, however, except for the parentheses after "result" in the last line. We'll eventually find a way to get rid of the parentheses, as well as the single quotes on the first line of code.

Before we can execute this internal DSL we need to write some code to process it. We will create all the code and the DSL in one file, and eventually split it into multiple files. In the end we'll have a good working solution.

Processing the DSL

We can readily process the first and last lines of the above DSL. So, let's comment out the rest of the lines and get started with them, as shown in Listing 3.

Listing 3. Processing the DSL

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"
}

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

We start by defining a HashMap named playersAndScores. In the players method, we populate the map with the names as keys and 0 as value for scores. In the result method, we iterate over the map looking for the player with maximum score and print the result. Given all scores are zero at this point (the scores are commented out), the result simply shows one of the players as winner with a score of 0, as shown below:

Winner is James with a score of 0

Now, we need to get down to the business of providing the scores. James 12 is simply James(12), so we need a method named James, and also methods named John and Jake. However, writing methods with these names is not going to help us if the names change. So, we will make use of the all-encompassing methodMissing method.

Listing 4. Use methodMissing for dynamic methods

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)
  }
}

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

In the methodMissing method we check to see if the method name is a valid key in the playersAndScores map. We also check whether the number of arguments presented is 1, and whether the argument is of type integer. You also can add checks that the score is greater than zero, if you like. If all the checks are true, we assign the score for the given player name. Otherwise, we generate an exception that the method called is not valid. The output from the above code is here:

Winner is John with a score of 14

The result above is correct for the given input file, but we're not done improving this DSL yet! First, we want to separate the processing of the DSL into a separate class. Then, we want to further refine the DSL using "essence over ceremony" as our guide. We'll first get rid of the parentheses, and then the single quotes. Let's see how we go about these tasks.

1 2 3 4 5 Page 3
Page 3 of 5