Creación de builders y
  DSL's con Groovy
    @neodevelop - @synergyj
Agenda
Groovy
Metaprogramación
Builders
DSL
{}
{}
Metaprogramación

•   Programas que escriben
    programas...

•   Soporte para agregar
    métodos y propiedades
    a un objeto en tiempo
    de ejecución...

•   Soporte de intercepción
    de llamadas, como AOP
Meta-object protocol
“Interpreta la semantica de un programa
abierto y extensible. Determina que
significa un programa y que
comportamiento tiene, así también, maneja
objetos que manipulan, crean, describen o
implementan otros objetos”
En Groovy podemos usar el MOP para
invocar métodos dinámicamente y
sintetizar clases y métodos al vuelo
MOP de Groovy
Todos los accesos a los métodos,
propiedades, constructores, operadores,
etc., pueden ser interceptados
El comportamiento en Java esta
fuertemente atado al tiempo de
compilación, en Groovy, el comportamiento
es adaptable en tiempo de ejecución
Meta-object Protocol
GroovyObject
 En Groovy se trabaja con 3 tipos de
 objetos:
    POJOs
    POGOs
    Groovy Interceptors
GroovyObject
package groovy.lang;

public interface GroovyObject {
    Object invokeMethod(String name, Object args);
    Object getProperty(String propertyName);
    void setProperty(String propertyName, Object newValue);
    MetaClass getMetaClass();
    void setMetaClass(MetaClass metaClass);
}
MetaClass
public interface MetaClass extends MetaObjectProtocol {
     Object invokeMethod(...);
     Object getProperty(...);
     void setProperty(...);
     Object invokeMissingMethod(...);
     Object invokeMissingProperty(...);
     Object getAttribute(...);
     void setAttribute(...);
     void initialize();
     List<MetaProperty> getProperties();
     List<MetaMethod> getMethods();
     ClassNode getClassNode();
     List<MetaMethod> getMetaMethods();
     int selectConstructorAndTransformArguments(...);
}
Intercepción de métodos - GroovyInterceptable
   // Definamos una clase que implemente GroovyInterceptable
   class ClaseInterceptada implements GroovyInterceptable{
   	
   	   def doMetodo(param){
   	   	   "Hola $param"
   	   }
   	
   	   //Tenemos que implementar este metodo de la interfaz
   	   def invokeMethod(String nombre, args){
   	   	   System.out.println "Ejecutando el metodo '$nombre' con argumentos '$args'"
   	   	   //Obtenemos el metodo a ejecutar de la clase
   	   	   def metodoValido = ClaseInterceptada.metaClass.getMetaMethod(nombre,args)
   	   	   //Si encontro el metodo a ejecutar...
   	   	   if(metodoValido != null){
   	   	   	   //Lo invocamos desde el metodo que encontro
   	   	   	   metodoValido.invoke(this, args)
   	   	   }else{
   	   	   	   //Si no lo encuentra simplemente llamamos al mŽtodo convencionalmente
   	   	   	   ClaseInterceptada.metaClass.invokeMethod(this, nombre, args)
   	   	   }
   	   }
   }

   def c = new ClaseInterceptada()
   println c.doMetodo("@grailsmx")
Intercepción de métodos - ExpandoMetaClass
   //Ahora usemos un interceptor en una clase que no es de nosotros
   Float.metaClass.invokeMethod = { String nombre, args ->
   	   //Al igual desplegamos algo de informacion
   	   System.out.println "Ejecutando el metodo '$nombre' con argumentos '$args'"
   	   //Obtenemos el metodo del metaClass
   	   def metodoValido = Float.metaClass.getMetaMethod(nombre,args)
   	   //Si no existe dicho metodo
   	   if(metodoValido == null){
   	   	   //Entonces regresamos la ejecucion convencional
   	   	   return Float.metaClass.invokeMissingMethod(delegate,nombre,args)
   	   }
   	   //Invocamos al metodo original con sus parametros
   	   resultado = metodoValido.invoke(delegate,args)
   	   //Regresamos la ejecucion del metodo
   	   resultado
   }

   //Usemos los metodos de la clase que no es propietaria
   println 10F.intValue()
   println 100F.toString()
   try{
   	   50F.empty()
   }catch(Exception e){
   	   println e.message
   }
MOP - Inyección de métodos(Categorías)
  //Definimos la clase que permitira ser la categoria
  class VerificaGramatica{
  	 //Para que un metodo pueda ser categorizado, debe ser static
  	 def static esPalindrome(String frase){
  	 	 //Implementamos nuestra categoria
  	 	 if(frase == new StringBuilder(frase).reverse().toString())
  	 	 	 true
  	 	 else
  	 	 	 false	
  	 }
  }

  //Con ayuda de la palabra reservada 'use' aplicamos la categoria
  use(VerificaGramatica){
  	 def frase = "anitalavalatina"
  	 println frase.class.name
  	 println "Es palindrome?: " + frase.esPalindrome()
  	 println frase.class.name
  }
MOP - Inyección de métodos(ExpandoMetaClass)


         //Podemos inyectar métodos estaticos
         Integer.metaClass.esPar = { ->
            delegate % 2 == 0
         }
         //Probemos nuestro método estatico
         println "2 es Par? " + 2.esPar()
         println "3 es Par? " + 3.esPar()
MOP - Síntesis de métodos(MethodMissing)
    class Persona{
    	   String nombre
    	   Map relaciones = [:]
    	
    	   def methodMissing(String relacion, personas){
    	   	   if(relaciones.containsKey(relacion)){
    	   	   	   personas.each{ persona ->
    	   	   	   	   relaciones.get(relacion).add(persona)
    	   	   	   }
    	   	   }else{
    	   	   	   relaciones.put(relacion,personas as List)
    	   	   }
    	   }
    }

    def juan = new Persona(nombre:'Juan')
    juan.trabajaEn("SynergyJ")
    juan.trabajaCon("Manuel","Jorge")
    juan.esAmigoDe("Domingo")
    juan.trabajaCon("Andres")
    juan.esAmigoDe("George")
    juan.conoceA("Susana","Perla","Cassandra","Alejandra")

    println juan.relaciones
MOP - Síntesis de métodos(ExpandoMetaClass)
     import java.text.NumberFormat

     def valoresDeConversion = ['USD':0.07973, 'EUR':0.0644,
                          'GBP':0.0538, 'JPY':7.2361]

     BigDecimal.metaClass.methodMissing = { String methodName, args ->
         tipoDeConversion = methodName[2..-1]
         valorDeConversion = valoresDeConversion[tipoDeConversion]

         if(valorDeConversion){
             NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.US)
             nf.setCurrency(Currency.getInstance(tipoDeConversion))
             return nf.format(delegate * valorDeConversion)
         }

         "No hay conversion de Pesos a ${tipoDeConversion}"
     }

     println 13.00.enUSD()
     println 16.00.enEUR()
     println 1.00.enXYZ()
Creación dinámica de clases con Expando
  def file = new File("migracion.csv")
  /*
   * Empresa,Nombre,Apellido,Puesto,correo...
   * 42 Claps,CŽsar,Salazar Hern‡ndez,CEO,c@42claps.com...
   * 4D Hispano,Dominique,Coste,Country Manager,d@4dhispano.com...
   */
  def sql = groovy.sql.Sql.newInstance("jdbc:hsqldb:file:/devDB","sa","","org.hsqldb.jdbcDriver")

  lines = file.readLines()
  atributos = lines[0].tokenize(",")
  //Creaci—n de Expando
  def persona = new Expando()
  atributos.each{
  	   //Creando atributos con Expando, tmb se pueden crear mŽtodos
  	   persona."${it.toLowerCase()}" = "${it.toLowerCase()}"
  }

  def insert = "insert into user(VERSION,PASSWD,ENABLED,USERNAME,EMAIL_SHOW,EMAIL,USER_REAL_NAME) "
  	   insert += "values(0,'7c4a8d09ca3762af61e59520943dc26494f8941b',true,?,true,?,?)"

  lines.each{ line ->
  	   valores = line.tokenize(",")
  	   index = 0
  	   atributos.each{
  	   	    persona."${it.toLowerCase()}" = "${valores[index]}"
  	   	    index++
  	   }
  	   //Uso de Expando
  	   sql.execute(insert,[persona.correo,persona.correo,"${persona.nombre} ${persona.apellido}"])
  }
¿Qué es un
 builder?
Builders
Son DSL’s internos que proveen trabajar facilmente
con ciertos tipos de problemas
De entrada, si tenemos la necesidad de trabajar
con ciertos tipos de estructuras o
representaciones, los builders son la mejor opción
Proveen una sintaxis que no ata con dicha
estructura o implementación
Solo son fachadas, por que en realidad no están
reemplazando la implementación, solamente
proveen una manera elegante de usarla
Groovy ya provee algunos, pero podemos hacer
los propios
Metaprogramación   Builders   BuilderSupport
//Instanciamos un builder
sqlBuilder = new MiSqlBuilder()
//Ejecutamos nuestro builder
sqlBuilder.build{
	 selecciona("campo1","campo2","campo3")
	 tabla("nombreDeTabla")
	 donde("1=1")
	 insertar('tabla',['campo1','campo2','campo3'],['valor1','valor2','valor3'])
	 ultimoInsert
}
class MiSqlBuilder{
	   def result = new StringWriter()
	   def build(closure){
	   	   closure.delegate = this
	   	   closure()
	   	   println result
	   }

	   def   methodMissing(String name,args){
	   	     switch(name){
	   	     	   case 'selecciona':
	   	     	   	   	   result << "nSELECT ${args.join(',')}"
	   	     	   	   break
	   	     	   case 'tabla':
	   	     	   	   	   result << " FROM ${args.join(',')}"
	   	     	   	   break
	   	     	   case 'donde':
	   	     	   	   	   result << " WHERE ${args[0]}n"
	   	     	   	   break
	   	     	   case 'insertar':
	   	     	   	   	   result << "nINSERT INTO ${args[0]}(${args[1].join(',')}) "
	   	     	   	   	   result << "VALUES('${args[2].join('','')}')n"
	   	     	   	   break
	   	     }
	   }

	   def   propertyMissing(String name){
	   	     if(name=="ultimoInsert"){
	   	     	   result << "nSELECT last_insert_id() n"
	   	     }
	   }
}
Lenguajes de dominio específico
DSL
Están enfocados a un cierto tipo de problema
La sintaxis está orientada al negocio(hay
expertos)
No lo usamos para resolver problemas de
propósito general como lo haría Java
Es pequeño, simple, expresivo y enfocado a
cierta área
Son manejados por el contexto y elocuentes
Con ayuda del MOP podemos crear DSL’s
¿Cómo desarrollar un DSL?
 Tipado dinámico y opcional
 La facilidad de usar Scripts
 ExpandoMetaClass
 Closures
 Sobrecarga de operadores
 Soporte de Builders
 Work-around de paréntesis
tomar 4.pastillas, de: lsd, en: 12.horas
class Droga {
	   String nombre
	   String toString() { nombre
	   }
}

class CantidadDeDroga {
	   int cantidad
	   String toString() {
	   	   cantidad == 1 ? "1 pastilla" : "$cantidad pastillas"
	   }
}

class TiempoDeDuracion {
	   Number numero
	   String unidad
}

Integer.metaClass.getPastillas = { -> new CantidadDeDroga(cantidad: delegate) }
Number.metaClass.getHoras = { -> new TiempoDeDuracion(numero: delegate, unidad:
"horas") }

def tomar(Map m, CantidadDeDroga cdd) {
	   println "Tomar $cdd de $m.de en $m.en.numero $m.en.unidad"
}

def oxicodona = new Droga(nombre: "Oxicodona")
def lsd = new Droga(nombre: "LSD")

tomar 2.pastillas, de: oxicodona, en: 6.horas
tomar 4.pastillas, de: lsd, en: 12.horas
Referencias
           groovy.codehaus.org
        grails.org.mx - @grailsmx
http://delicious.com/neodevelop/groovy
   Programming Groovy - Venkat S.
¡Gracias!
  Q &A
@neodevelop

Creación de Builders y DSL's con Groovy

  • 1.
    Creación de buildersy DSL's con Groovy @neodevelop - @synergyj
  • 2.
  • 5.
  • 7.
  • 8.
    Metaprogramación • Programas que escriben programas... • Soporte para agregar métodos y propiedades a un objeto en tiempo de ejecución... • Soporte de intercepción de llamadas, como AOP
  • 9.
    Meta-object protocol “Interpreta lasemantica de un programa abierto y extensible. Determina que significa un programa y que comportamiento tiene, así también, maneja objetos que manipulan, crean, describen o implementan otros objetos” En Groovy podemos usar el MOP para invocar métodos dinámicamente y sintetizar clases y métodos al vuelo
  • 10.
    MOP de Groovy Todoslos accesos a los métodos, propiedades, constructores, operadores, etc., pueden ser interceptados El comportamiento en Java esta fuertemente atado al tiempo de compilación, en Groovy, el comportamiento es adaptable en tiempo de ejecución
  • 11.
    Meta-object Protocol GroovyObject EnGroovy se trabaja con 3 tipos de objetos: POJOs POGOs Groovy Interceptors
  • 12.
    GroovyObject package groovy.lang; public interfaceGroovyObject { Object invokeMethod(String name, Object args); Object getProperty(String propertyName); void setProperty(String propertyName, Object newValue); MetaClass getMetaClass(); void setMetaClass(MetaClass metaClass); }
  • 13.
    MetaClass public interface MetaClassextends MetaObjectProtocol { Object invokeMethod(...); Object getProperty(...); void setProperty(...); Object invokeMissingMethod(...); Object invokeMissingProperty(...); Object getAttribute(...); void setAttribute(...); void initialize(); List<MetaProperty> getProperties(); List<MetaMethod> getMethods(); ClassNode getClassNode(); List<MetaMethod> getMetaMethods(); int selectConstructorAndTransformArguments(...); }
  • 14.
    Intercepción de métodos- GroovyInterceptable // Definamos una clase que implemente GroovyInterceptable class ClaseInterceptada implements GroovyInterceptable{ def doMetodo(param){ "Hola $param" } //Tenemos que implementar este metodo de la interfaz def invokeMethod(String nombre, args){ System.out.println "Ejecutando el metodo '$nombre' con argumentos '$args'" //Obtenemos el metodo a ejecutar de la clase def metodoValido = ClaseInterceptada.metaClass.getMetaMethod(nombre,args) //Si encontro el metodo a ejecutar... if(metodoValido != null){ //Lo invocamos desde el metodo que encontro metodoValido.invoke(this, args) }else{ //Si no lo encuentra simplemente llamamos al mŽtodo convencionalmente ClaseInterceptada.metaClass.invokeMethod(this, nombre, args) } } } def c = new ClaseInterceptada() println c.doMetodo("@grailsmx")
  • 15.
    Intercepción de métodos- ExpandoMetaClass //Ahora usemos un interceptor en una clase que no es de nosotros Float.metaClass.invokeMethod = { String nombre, args -> //Al igual desplegamos algo de informacion System.out.println "Ejecutando el metodo '$nombre' con argumentos '$args'" //Obtenemos el metodo del metaClass def metodoValido = Float.metaClass.getMetaMethod(nombre,args) //Si no existe dicho metodo if(metodoValido == null){ //Entonces regresamos la ejecucion convencional return Float.metaClass.invokeMissingMethod(delegate,nombre,args) } //Invocamos al metodo original con sus parametros resultado = metodoValido.invoke(delegate,args) //Regresamos la ejecucion del metodo resultado } //Usemos los metodos de la clase que no es propietaria println 10F.intValue() println 100F.toString() try{ 50F.empty() }catch(Exception e){ println e.message }
  • 16.
    MOP - Inyecciónde métodos(Categorías) //Definimos la clase que permitira ser la categoria class VerificaGramatica{ //Para que un metodo pueda ser categorizado, debe ser static def static esPalindrome(String frase){ //Implementamos nuestra categoria if(frase == new StringBuilder(frase).reverse().toString()) true else false } } //Con ayuda de la palabra reservada 'use' aplicamos la categoria use(VerificaGramatica){ def frase = "anitalavalatina" println frase.class.name println "Es palindrome?: " + frase.esPalindrome() println frase.class.name }
  • 17.
    MOP - Inyecciónde métodos(ExpandoMetaClass) //Podemos inyectar métodos estaticos Integer.metaClass.esPar = { -> delegate % 2 == 0 } //Probemos nuestro método estatico println "2 es Par? " + 2.esPar() println "3 es Par? " + 3.esPar()
  • 18.
    MOP - Síntesisde métodos(MethodMissing) class Persona{ String nombre Map relaciones = [:] def methodMissing(String relacion, personas){ if(relaciones.containsKey(relacion)){ personas.each{ persona -> relaciones.get(relacion).add(persona) } }else{ relaciones.put(relacion,personas as List) } } } def juan = new Persona(nombre:'Juan') juan.trabajaEn("SynergyJ") juan.trabajaCon("Manuel","Jorge") juan.esAmigoDe("Domingo") juan.trabajaCon("Andres") juan.esAmigoDe("George") juan.conoceA("Susana","Perla","Cassandra","Alejandra") println juan.relaciones
  • 19.
    MOP - Síntesisde métodos(ExpandoMetaClass) import java.text.NumberFormat def valoresDeConversion = ['USD':0.07973, 'EUR':0.0644, 'GBP':0.0538, 'JPY':7.2361] BigDecimal.metaClass.methodMissing = { String methodName, args -> tipoDeConversion = methodName[2..-1] valorDeConversion = valoresDeConversion[tipoDeConversion] if(valorDeConversion){ NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.US) nf.setCurrency(Currency.getInstance(tipoDeConversion)) return nf.format(delegate * valorDeConversion) } "No hay conversion de Pesos a ${tipoDeConversion}" } println 13.00.enUSD() println 16.00.enEUR() println 1.00.enXYZ()
  • 20.
    Creación dinámica declases con Expando def file = new File("migracion.csv") /* * Empresa,Nombre,Apellido,Puesto,correo... * 42 Claps,CŽsar,Salazar Hern‡ndez,CEO,c@42claps.com... * 4D Hispano,Dominique,Coste,Country Manager,d@4dhispano.com... */ def sql = groovy.sql.Sql.newInstance("jdbc:hsqldb:file:/devDB","sa","","org.hsqldb.jdbcDriver") lines = file.readLines() atributos = lines[0].tokenize(",") //Creaci—n de Expando def persona = new Expando() atributos.each{ //Creando atributos con Expando, tmb se pueden crear mŽtodos persona."${it.toLowerCase()}" = "${it.toLowerCase()}" } def insert = "insert into user(VERSION,PASSWD,ENABLED,USERNAME,EMAIL_SHOW,EMAIL,USER_REAL_NAME) " insert += "values(0,'7c4a8d09ca3762af61e59520943dc26494f8941b',true,?,true,?,?)" lines.each{ line -> valores = line.tokenize(",") index = 0 atributos.each{ persona."${it.toLowerCase()}" = "${valores[index]}" index++ } //Uso de Expando sql.execute(insert,[persona.correo,persona.correo,"${persona.nombre} ${persona.apellido}"]) }
  • 21.
    ¿Qué es un builder?
  • 22.
    Builders Son DSL’s internosque proveen trabajar facilmente con ciertos tipos de problemas De entrada, si tenemos la necesidad de trabajar con ciertos tipos de estructuras o representaciones, los builders son la mejor opción Proveen una sintaxis que no ata con dicha estructura o implementación Solo son fachadas, por que en realidad no están reemplazando la implementación, solamente proveen una manera elegante de usarla Groovy ya provee algunos, pero podemos hacer los propios
  • 23.
    Metaprogramación Builders BuilderSupport
  • 24.
    //Instanciamos un builder sqlBuilder= new MiSqlBuilder() //Ejecutamos nuestro builder sqlBuilder.build{ selecciona("campo1","campo2","campo3") tabla("nombreDeTabla") donde("1=1") insertar('tabla',['campo1','campo2','campo3'],['valor1','valor2','valor3']) ultimoInsert }
  • 25.
    class MiSqlBuilder{ def result = new StringWriter() def build(closure){ closure.delegate = this closure() println result } def methodMissing(String name,args){ switch(name){ case 'selecciona': result << "nSELECT ${args.join(',')}" break case 'tabla': result << " FROM ${args.join(',')}" break case 'donde': result << " WHERE ${args[0]}n" break case 'insertar': result << "nINSERT INTO ${args[0]}(${args[1].join(',')}) " result << "VALUES('${args[2].join('','')}')n" break } } def propertyMissing(String name){ if(name=="ultimoInsert"){ result << "nSELECT last_insert_id() n" } } }
  • 26.
  • 27.
    DSL Están enfocados aun cierto tipo de problema La sintaxis está orientada al negocio(hay expertos) No lo usamos para resolver problemas de propósito general como lo haría Java Es pequeño, simple, expresivo y enfocado a cierta área Son manejados por el contexto y elocuentes Con ayuda del MOP podemos crear DSL’s
  • 28.
    ¿Cómo desarrollar unDSL? Tipado dinámico y opcional La facilidad de usar Scripts ExpandoMetaClass Closures Sobrecarga de operadores Soporte de Builders Work-around de paréntesis
  • 29.
    tomar 4.pastillas, de:lsd, en: 12.horas
  • 30.
    class Droga { String nombre String toString() { nombre } } class CantidadDeDroga { int cantidad String toString() { cantidad == 1 ? "1 pastilla" : "$cantidad pastillas" } } class TiempoDeDuracion { Number numero String unidad } Integer.metaClass.getPastillas = { -> new CantidadDeDroga(cantidad: delegate) } Number.metaClass.getHoras = { -> new TiempoDeDuracion(numero: delegate, unidad: "horas") } def tomar(Map m, CantidadDeDroga cdd) { println "Tomar $cdd de $m.de en $m.en.numero $m.en.unidad" } def oxicodona = new Droga(nombre: "Oxicodona") def lsd = new Droga(nombre: "LSD") tomar 2.pastillas, de: oxicodona, en: 6.horas tomar 4.pastillas, de: lsd, en: 12.horas
  • 31.
    Referencias groovy.codehaus.org grails.org.mx - @grailsmx http://delicious.com/neodevelop/groovy Programming Groovy - Venkat S.
  • 32.
    ¡Gracias! Q&A @neodevelop