ICE09 . playing with java, scala, groovy and spring .

Playing with Spring

DSLs made easy – with Groovy’s chain method calls (GEP 3)

Posted by ice09 on October 7, 2011

As you probably know, the creation of DSLs is quite easy these days. Take Groovy or Scala and the creation of internal DSLs is a day’s work. Take Xtext 2.0 and the creation of an external DSL is a two days’s work.

However, let’s assume you do not have a day, but two hours left – still possible? Yes, indeed. With Groovy 1.8 and its new chained method calls (contributed by GEP 3).

So, chained method calls have always be possible in Groovy, but the new “conventions” of GEP 3 make the creation of a DSL really straightforward, since with a little effort you can design a DSL which has no brackets and dots at all!

.use case.

A sample use case for a simple DSL is the evaluation of account data. I wanted to have

  • the possibility to parse downloaded CSV files
  • enrich the items with certain categories
  • store them in a local (file based) database
  • be able to evaluate the items for certain time spans and categories

Having thought about the GUI which I would have to create for this use case, I felt overwhelmed immediately, given that my design capabilities are … debatable. But also, I would like to be able to use the data in several ways, transform it, check and validate it and so on. For this I do not want to create my own command set, but I want to use Groovy commands for this. Namely, I want to create an internal DSL.

.sample.

The following commands I can think of. However, this is not complete

load savingsfile     scans a predefined directory for the newest CSV file with a certain pattern and reads this file into memory 
                     (as a list of string arrays)
filter database      filter the data in memory against a predefined database file and remove all items from memory which are in the 
                     database already (depends on load step)
categorize data      use a certain rule set (external configuration) to automatically categorize the items according to different properties
                     (like sender/receiver of money)
show data            show the actual data in memory
save database        store the actual data in database file (filter step must be executed first)

Moreover, the DSL should allow for evaluating the data in memory, with the following syntax

evaluation of date september, 2011 category "Clubbing" with display           evaluate the database and display evaluation for 
                                                                              september, 2011 in category "Clubbing"
evaluation of date always category "Clubbing" without display                 same as above, but for no specified time span
evaluation of date always category all with display                           same as above, but for no specified time span and no specified category
evaluation of date always category all without display                        same as above, but without display of data (yes, it might not make sense -
                                                                              but it's possible :))

Now, the thing is that I would like to be able to execute all these commands several times, for different data and – since the data is available as a list of string arrays – I know I can do whatever I want with it within Groovy.
So, if I do not have a GUI, what about a shell? Of course, Groovy has one. What about TAB completion on my shiny new DSL – sure, why not? (ok, honestly, due to type inferencing difficulties, this does not work completely, but it’s a help definitely – just try it!)

So, let’s go. Just start the Groovy shell (GROOVY_HOME/bin/groovysh) and copy & paste this code. Yes, that’s just the skeleton to show the important parts – but there is a working version here, which works if you are customer of the German Sparkasse (shouldn’t they pay me for this?). If you really want to use this “real”, working version, you will need a rule file, which must be named rules.properties and has key/value pairs of regular expressions for the sender field like PAYPAL=Paypal or ^GA=Geldautomat.

class Savings {
    def read = false
    def filtered = false
    def datenbank
    Savings(datenbank) {
        this.datenbank = datenbank
    }
    def load(sfile) {
        read = true
        println "file loaded."
        this
    }
    def display(dummy) {
        if (!read) println "no data available."
        else println "display called."
    }
    def filter(sfile) {
        filtered = true
        println "data is filtered."
        this
    }
    def categorize(cat) {
        println "categorize called with category ${cat}."
    }
    def save(sfile) {
        if (!filtered) println "data is not filtered, aborting storing."
        else println "data is stored in file $datenbank"; read = false
    }
}

class Evaluation {
    def month
    def year
    def display = false
    def category
    def date(Integer month, Integer year) {
        this.month = month
        this.year = year
        this
    }
    def date(all) {
        this.month = null
        this.year = null
        this
    }
    def without(display) {
        this.display = false
        evaluate()
    }
    def with(ausgabe) {
        this.display = true
        evaluate()
    }
    def category(category) {
        if (category.toString() != "all")
            this.category = category
        this
    }
    def evaluate() {
        println "evaluate ${(month != null) ? "month ${month+1} year $year" : "all"} in category $category ${display ? "with":"without"} display"
    }
}

enum words { savingsfile, category, of, data, database, all, always, with, without, display }
(january, february, march, april, may, june, july, august, september, october, november, december) = (0..11)
datenbank = "db.csv"
import static words.*

savings = new Savings(datenbank)

read = { file -> savings.load(file) }
showit = { speicher -> savings.display()}
filter = { file -> savings.filter(file)}
categorize = { speicher -> savings.categorize(speicher)}
store = { file -> savings.save(file)}
evaluation = { of -> new Evaluation() }

//sample commands

read savingsfile
filter database
categorize data
showit data
store database

evaluation of date september, 2011 category "Clubbing" with display
evaluation of date always category "Clubbing" without display
evaluation of date always category all with display
evaluation of date always category all without display

The last lines (from //sample commands) are actual commands of the DSL. Try these with TAB completion in the shell and see what’s possible and what’s not. I will not explain the functionality in detail, because there are several resources which can be used for clarification.

.conclusion.

I wanted to show that two hours can be enough to create your own DSL, together with the Groovy shell this is a really powerful way to let people with limited technical abilities use the power of the complete Java/Groovy platform in interactive mode! Imagine the possibilities – your business analysts will love you.

Obviously, I had to fit the DSL to the conventions of GEP 3 (and moreover, I had to rename “load”, “show” and “save”, since these are reserved keywords in the Groovy shell…), but still I think it’s worth it. But if you take care of DSL design and do not want the language to be restricted by the command chain conventions, look for a cleaner approach. For quick & dirty API usage easing, this works as a charm.

If this blog post was useful to you, you can tip me. Maybe you do not want to tip, but please inform yourself about Bitcoin and applications of Bitcoin like Youttipit.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: