Sunday, March 28, 2010

DSL using Groovy

Last night, I was playing with dynamic language groovy to write my very own DSL (domain specific language). In this blog I will explain how to write DSL using groovy.

This blog assumes the programming knowledge of groovy and underlying concept where & why DSL should be used.

Lets starts with my aim. My aim is to write dsl like “take 1 pill of disprin after 10 hours”. 

More specifically,

take 1.pill,
    of: disprin,
    after: 10.hours


Write the above script in IDE of your choice. Try to execute the above script.  After execution, following error will result

Caught: groovy.lang.MissingPropertyException: No such property: disprin ..

Quiet logical. Groovy cannot understand “disprin”. Let's add dynamically the property – disprin

this.metaClass.getProperty= {name->
  def metaProperty= this.metaClass.getMetaProperty(name)
    //'getMetaProperty' gets property, which may be an added or an existing one
  metaProperty? metaProperty.getProperty(delegate): "" + delegate
}


Let’s run the script again. After running the script, following exception came

Caught: groovy.lang.MissingPropertyException: No such property: hours for class: java.lang.Integer

The above error tells that, groovy cannot find the property hours in the class Integer. Groovy can magically add the properties to any java class and any jdk class as shown below

Number.metaClass.getHours = { -> new Integer(delegate          )
            println ("getHours delegate >> " + delegate)
}

After running the script again, the following error came

Caught: groovy.lang.MissingPropertyException: No such property: pill for class: java.lang.Integer


Lets add the property – pill to the Integer



Number.metaClass.getPill = { -> new Integer(delegate          )
            println ("getPill delegate >> " + delegate)
}

Let run the exception again; now the following exception came
Caught: groovy.lang.MissingMethodException: take()

Finally, lets add the missing method – take. The missing method can added dynamically at runtime as shown below

metaClass.invokeMethod= {name, args->
            def metaMethod= this.metaClass.getMetaMethod(name, args)
            //'getMetaMethod' gets method, which may be an added or an existing one
            // Call out to missing method can be handled here
            metaMethod? metaMethod.invoke(delegate,args): name
}


Hence the complete script looks like

this.metaClass.getProperty= {name->
  def metaProperty= this.metaClass.getMetaProperty(name)
    //'getMetaProperty' gets property, which may be an added or an existing one
  metaProperty? metaProperty.getProperty(delegate): "" + delegate
}

Number.metaClass.getHours = { -> new Integer(delegate          )
}

Number.metaClass.getPill = { -> new Integer(delegate          )
}

metaClass.invokeMethod= {name, args->
            def metaMethod= this.metaClass.getMetaMethod(name, args)
            //'getMetaMethod' gets method, which may be an added or an existing one
            // Call out to missing method can be handled here
            metaMethod? metaMethod.invoke(delegate,args): name
}

/*
 * Just to test
 * println (take (1.pill,of:disprin,after: 6.hours))
 */

take 1.pill,
     of:disprin,
     after:6.hours

Conclusion

            With groovy it’s very simple to create a DSL. Also the groovy specify code mentioned below

this.metaClass.getProperty= {name->
  def metaProperty= this.metaClass.getMetaProperty(name)
    //'getMetaProperty' gets property, which may be an added or an existing one
  metaProperty? metaProperty.getProperty(delegate): "" + delegate
}

Number.metaClass.getHours = { -> new Integer(delegate          )
}

Number.metaClass.getPill = { -> new Integer(delegate          )
}

metaClass.invokeMethod= {name, args->
            def metaMethod= this.metaClass.getMetaMethod(name, args)
            //'getMetaMethod' gets method, which may be an added or an existing one
            // Call out to missing method can be handled here
            metaMethod? metaMethod.invoke(delegate,args): name
}

can be abstracted out in the domain model and hence DSL will simply looks like


take 1.pill,
     of:disprin,
     after:6.hours

No comments: