Intro
Una de las características que más se extrañan a la hora de utilizar GWT es la capacidad de Java de realizar introspección sobre beans. La causa de esta limitación es que GWT no tiene emulación la API reflexión (java.lang.reflect). En este artículo vamos a ver como podemos implementar una suerte de introspección en GWT. También vamos a explorar algunas aplicaciones de introspección, ya que en principio no es trivial su utilidad.
What for?
Lo primero que vamos a ver es para qué queremos esto.
Supongamos que queremos desarrollar un framework de componentes visuales que tenga data binding (capacidad de “enganchar” la propiedad de un objeto a un componente visual). Seguramente a todo aquel que le tocó la “maravillosa” tarea de implementar un CURD (ABM en criollo) se topó con la tediosa tarea de setear y leer de los componentes visuales al objeto y viceversa. Si te tocó hacer esto, algo malo hiciste
. Bueno, esta es una tarea tan aburrida como propensa a errores, sobre todo de Control-C + Control-V. Si pudieramos decir algo como: “este componente UI mira la propiedades tal de cual objeto”, entonces podemos aliviar bastante esta tarea ya que por ejemplo nos ahorramos todo el código de manejo de eventos.
Entonces si podemos hacer esto, podemos construir un conjunto de componentes a los cuales simplemente le seteamos el bean y la propiedad que “mira” así cuando al componente se le cambia el valor, automáticamente se refleja en el bean y viceversa.
Pero ¿cómo hacer esto sin introspeccion?
How? What?!
Como vimos, en GWT (al menos hasta la 1.7) no hay reflexión, por lo que no nos queda más remedio que buscar otra forma de implementar introspección. Primero vamos a ver qué es la introspección en Java. Según el JavaDoc de la clase java.beans.Introspector:
“The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.”
En español:
“La clase Introspector provee una forma estándar para que herramientas descubran propiedades, eventos y métodos soportados por un Java Bean”.
La parte que más nos interesa es la parte de las propiedades. O sea, queremos poder implementar un mecanismo con el cual se le pueda preguntar a un Java Bean cuales son sus propiedades y cuales son los valores de estas.
La forma que vamos a implementar es creando una clase que sea la introspección para cada clase que queramos tener introspección.
Entonces para la clase:
public class Persona {
private String nombre;
private String direccion;
private Date nacimiento;
private boolean sexo;
public Persona() {
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
// ...más getters y setters...
}
Tenemos que implementar la clase de “introspección”:
public PersonaIntrospection {
private Persona persona;
public PersonaIntrospeccion() {
}
public Persona getPersona() {
return persona;
}
public Persona setPersona(Persona p) {
persona = p;
}
public List<String> getProperties() {
List<String> l = new ArrayList<String>();
l.add(“nombre”);
l.add(“direccion”);
l.add(“nacimiento”);
l.add(“sexo”);
}
public Object getProperty(String property) {
if (“nombre”.equals(property))
return persona.getNombre();
else if (“direccion”.equals(property))
return persona.getDireccion();
else if (“nacimiento”.equals(property))
return persona.getNacimiento();
else if (“sexo”.equals(property))
return persona.getSexo();
else
throw new IllegalArgumentException(property);
}
public void setProperty(String property, Object value) {
if (“nombre”.equals(property))
persona.setNombre((String)value);
else if (“direccion”.equals(property))
persona.setDireccion((String)value);
else if (“nacimiento”.equals(property))
persona.setNacimiento((Date)value);
else if (“sexo”.equals(property))
persona.setSexo((Boolean)value);
else
throw new IllegalArgumentException(property);
}
}
Más allá de las optimizaciones que se le pueden hacer al código (como cachear la lista de propiedades), la idea es que se entienda de por qué esta clase nos da introspección sobre un objeto persona. Como ya se deben estar imaginando, la gracia es no tener que escribir la clase PersonaIntrospection. La gracia es trabajar menos en cosas aburridas y más en cosas “divertidas“
Enter generators!
Lo que vamos a hacer es que GWT genere la clase PersonaIntrospection. ¿Cómo? Usando generators. Esta es una característica bien interesante de GWT. El foco de este artículo no está en explicar generators, hay mucha literatura en Internet. Acá va un link http://code.google.com/intl/es/webtoolkit/doc/1.6/DevGuideCodingBasics.html#DevGuideDeferredBinding. Igual no hay que ser un experto para entender lo que vamos a hacer.
Básicamente vamos a escribir una clase que genere el código de XxxIntrospection para la clase Xxx según vimos en el ejemplo de Persona. Y (para no tener que realizar ningún paso previo a la compilación generando la clase con alguna utilidad externa) vamos a usar la API que tiene GWT que permite extender el compilador (com.google.gwt.core.ext). NOTA: Los que hayan utilizado APT de Java van ver unas cuantas similitudes (http://java.sun.com/javase/6/docs/technotes/guides/apt/index.html).
Cuando el compilador de GWT encuentra con la instrucción GWT.create(class) –que devuelve una implementación de la interfaz class– invoca al generador asociado a la interfaz class (esta asociación se declara en el gwt.xml del módulo). En este generador se “escribe” el código que implementa la interfaz class. Para escribir este código tenemos la asistencia de la API com.google.gwt.core.ext.typeinfo que recuerda mucho a la API Mirror Reflection de Java.
Generate
Ahora vamos a ver el generador en si. Pero primero vamos a ver como se utiliza. GWT.create() toma una interfaz y devuelve una implementación de la misma. Entonces vamos definir una interfaz de marca y la vamos a llamar Introspectable. Toda clase que queramos tener soporte de introspección va a tener que implementar esta intefaz:
public interface Introspectable {
}
Entonces la clase Persona:
public class Persona implements Introspectable {
// ... atributos y métodos ...
}
Además, tenemos que tener la interfaz de idioma de la introspección:
public interface Introspection<I extends Introspectable> {
I getIntrospectable();
void setIntrospectable(I i);
<T> T getProperty(String propName);
void setProperty(String propName, Object value);
Collection<String> getPropertyNames();
}
Y para obtener una introspección y usarla:
Persona persona = new Persona();
Introspection<Persona> instrospection = GWT.create(Persona.class);
instrospection.setIntrospectable(persona);
for (String prop : instrospection.getPropertyNames()) {
System.out.println(prop + " = " + instrospection.getProperty(prop));
}
String nombre = instrospection.getProperty("nombre");
String direccion = instrospection.getProperty("direccion");
Date nacimiento = instrospection.getProperty("nacimiento");
Boolean sexo = instrospection.getProperty("sexo");
Para asociar el generador con la interfaz Introspectable, agregamos el siguiente fragmento al :
<generate-with class="com.aquait.utils.gwt.rebind.IntrospectionGenerator">
<when-type-assignable class="com.aquait.utils.gwt.introspection.Introspectable"/>
</generate-with>
Así, cuando el compilador de GWT encuentra GWT.create(Persona.class), llama al generador pues la clase Persona implementa Introspectable. Luego, tenemos una implementación de la interfaz Introspection para la clase Persona.
Para usarlo en un proyecto, hay que agregar esto en el <modulo>.gwt.xml:
<inherits name="com.aquait.utils.gwt.Introspection"/>
El módulo GWT se puede descargar de acá: introspection4gwt
EDIT: se actualizó el archivo debido a un pequeño bug.
EDIT: se volvió a actualizar debido a un bug que hacía que no se tomaran en cuenta las propiedades de las super clases.
