Ir al contenido principal

EJEMPLO REFLECTION EN JAVA

Hoy vamos a realizar un ejemplo completo con el API Reflection de Java. Con este API podemos por ejemplo obtener el nombre y el valor de los atributos de cualquier clase, crear una instancia de ella utilizando cualquiera de sus constructores o ejecutar sus métodos.

El objetivo del artículo es crear un clase genérica que a partir de una lista genérica de items sea capaz de generar código JSON cuyo nombre sea el resultado de la ejecución de un método concreto y como parámetros sean los nombres y valores de de sus atributos.

Por tanto en el ejemplo aglutinamos varios conceptos que seguramente nos encontraremos a lo largo de nuestra trayectoria profesional como programadores:

  • API Reflection
  • Genericidad
  • JSON, muy utilizado para el intercambio de información entre aplicaciones. La implementación que se utiliza para realizar el mapping es la librería jackson.



Creamos una clase parametrizada por una clase. Dicha clase recibirá una lista de items de una clase determinada G. Definimos como constantes varios prefijos que seran los que utilizamos para dar nombre a cada nodo JSON para genera el código asociado:


  •  item, se utiliza a nivel global para cada elemento de la clase G de la lista de items (item1, item2, itemN)

  • name, representa el nombre de cada item. En este ejemplo se extraerá de llamar al método llamado getItemNameValue. Si la clase G no implementa este método, se devolverá la cadena vacia.

  • data, representa los parámetros del item. Dichos parámetros se extraen de los atributos de la clase G que tengan un método get publico asociado.

  • name, value, representan el nombre y el valor de cada atributo de la clase.


public class JsonGenerator<G> {

    private static final String METHOD_ITEM_NAME = "getItemNameValue";
    private static final String PREFFIX_DATA = "data";
    private static final String PREFFIX_ITEM = "item";
    private static final String PREFFIX_NAME = "name";
    private static final String PREFFIX_VALUE = "value";
    private static final String GET = "get";

    /**
     * Lista de items que se parsea para obtener la salida JSON
     */
    private List items;

A continuación creamos un método que obtiene el valor del método getItemNamevalue. Es un método que obtiene una instancia de la clase java.lang.reflect.Method para invocar un determinado método de una clase genérica G. El método se obtiene con la operación getDeclaredMethod de la clase Class pasando como parámetro el nombre del método. Utilizando el método invoke de la clase Method ya tenemos el valor de cada item.

NOTA: Notad que se utiliza la nueva notación de Java 7  para la definición de excepcion con los separadores barra.


private String getItemNameValue(Class cls) {
        // Buscando el método
        Method method = null;
        // no paramater  
        Class noparams[] = {};
        try {
            Object obj = cls.newInstance();
            method = cls.getDeclaredMethod(METHOD_ITEM_NAME, noparams);
            String res = (String) method.invoke(obj, new Object[0]);
            return res;
        } catch (SecurityException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
            return StringUtils.EMPTY;
        }
    }

Tendremos un método para obtener los campos de la clase G. Este método devuelve un array de la clase del API Reflection java.lang.reflect.Field. Esta clase representa un atributo concreto de una clase y nos servirá como punto de partida para acceder al nombre como al valor del mismo. Observad que el método es recursivo, primero obtiene los atributos de la clase y posteriormente se llama a sí mismo para recuperar los atributos definidos en la superclase. Fijaos también que los atributos con el modificador static se evita añadirlos a la lista, sólo se recuperan los atributos sin el modificador static para evitar añadir constantes.


private Field[] getAllFields(Class klass) {
        List fields = new ArrayList<>();
        fields.addAll(Arrays.asList(klass.getDeclaredFields()));
        if (klass.getSuperclass() != null) {
            fields.addAll(Arrays.asList(getAllFields(klass.getSuperclass())));
        }

        Iterator fAux = fields.iterator();
        while (fAux.hasNext()) {
            Field field = fAux.next();
            if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                fAux.remove();
            }
        }
        return fields.toArray(new Field[]{});
    }

Una vez que tengamos todos los atributos de la instancia de una clase necesitaremos métodos para acceder a su nombre e invocar al método get asociado para obtener su valor. Estos dos métodos se definen a continuación:


private Object getFieldValue(Object userInstance, Field field) {
        try {
            String methodName = GET + StringUtils.capitalize(field.getName());
            return MethodUtils.invokeExactMethod(userInstance, methodName, new Object[0]);        
        } catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            return StringUtils.EMPTY;
        }
    }

private List<String> getFieldsNames(Field[] fields) {
        List l = new ArrayList<>();
        for (Field field : fields) {
            l.add(field.getName());
        }
        return l;
    }

private List<String> getFieldsValues(Field[] fields, Object obj) {
        List l = new ArrayList<>();
        for (Field field : fields) {
            Object value = getFieldValue(obj, field);
            l.add((value == null) ? StringUtils.EMPTY : value.toString());
        }
        return l;
    }


Una vez que tenemos toda la información de la clase G únicamente nos queda generar el código JSON. Para ello vamos a utilizar la clase org.codehaus.jackson.node.ObjectNode, que representa un elemento JSON y la clase org.codehaus.jackson.node.JsonNodeFactory que se encargará de ir creando cada nodo JSON. Finalmente con la clase org.codehaus.jackson.map.ObjectMapper generaremos una cadena a partir de un nodo JSON raíz que representa el código JSON de la clase.


 private void addSimpleObject(ObjectNode node, String name, String value) {
        node.put(name, value);
    }

private void addComplexObject(ObjectNode node, String name, ObjectNode value) {
        node.put(name, value);
    }

private void addListObject(ObjectNode node, List names, List values) {

        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            String value = values.get(i);
            ObjectNode atributos = factory.objectNode();
            atributos.put(preffixName, name);
            atributos.put(preffixValue, (value == null) ? StringUtils.EMPTY : value);
            node.put(preffixData + (i + 1), atributos);
        }

    }


Y el método pegamento que utiliza todo lo anterior para generar el código JSON:



public String generateJson() throws JsonGenerationException, JsonMappingException, IOException {
        if (CollectionUtils.isEmpty(items)) {
            return StringUtils.EMPTY;
        }
        for (int i = 0; i < items.size(); i++) {
            G item = items.get(i);
            ObjectNode elementoItem = factory.objectNode();

            String itemNameValue = getItemNameValue(item.getClass());
            addSimpleObject(elementoItem, preffixName, itemNameValue);

            List<String> names = getFieldsNames(getAllFields(item.getClass()));
            List<String> values = getFieldsValues(getAllFields(item.getClass()), item);

            addListObject(elementoItem, names, values);
            addComplexObject(root, preffixItem + (i + 1), elementoItem);
        }     
        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
    }


Para cada uno de los elementos de la lista de tipo G:


  • Se añade el nodo JSON simple con el nombre del item (instancia de clase G) al nodo JSON que representa al item.
  • Se añade el nodo JSON complejo para cada uno de los atributos del item (instancia de clase G) al nodo JSON que representa al item.
  • Se añade el nodo JSON que representa al item al nodo raiz.

Vamos a ejecutar la clase con una lista de 3 items de tres clases diferentes dentro de una jerarquía: Nivel1, Nivel2 y Nivel3. Nivel1 es padre de Nivel2 y Nivel2 es a su vez padre de Nivel. Cada una de ellas encapsula un atributo llamado campo1, campo2 y campo3 respectivamente.




...
...
     List<nivel1> elementos = new ArrayList<>();

     Nivel1 nivel1 = new Nivel1("1");
     Nivel2 nivel2 = new Nivel2("2", "3");
     Nivel3 nivel3 = new Nivel3("4","5","6");
     
     elementos.add(nivel1);
     elementos.add(nivel2);
     elementos.add(nivel3);
     
     JsonGenerator<nivel1> jsonGenerator = new JsonGenerator<>();
     jsonGenerator.setItems(elementos);
  
     System.out.println(jsonGenerator.generateJson());
...
...


La salida para este caso concreto sería:

{
  "item1" : {
    "name" : "",
    "data1" : {
      "name" : "campo1",
      "value" : "1"
    }
  },
  "item2" : {
    "name" : "",
    "data1" : {
      "name" : "campo1",
      "value" : "2"
    },
    "data2" : {
      "name" : "campo2",
      "value" : "3"
    }
  },
  "item3" : {
    "name" : "",
    "data1" : {
      "name" : "campo1",
      "value" : "4"
    },
    "data2" : {
      "name" : "campo2",
      "value" : "5"
    },
    "data3" : {
      "name" : "campo3",
      "value" : "6"
    }
  }
}

El código del proyecto en Netbeans lo podéis descargar de aquí.

Saludos.

Comentarios

Entradas populares de este blog

Soluciones Alchemy Classic 389 elementos

Hace algún tiempo salió una actualización del Juego Alchemy Classic en la que aparecían más elementos (389 en lugar de 238). Aparte de añadir elementos mejoran algunas traducciones en castellano y mejoran la interfaz, aunque todavía hay algún error en algunos nombres de elementos. Aquí os dejo las soluciones para los que estén atascados y no puedan dormir por las noches: Sustancia primaria Aire=Elemento primario  Fuego=Elemento primario  Agua=Elemento primario  Tierra=Sustancia Primaria Arena=Piedra + Aire Piedra=Tierra + Fuego Arcilla=Arena + Pantano Caliza=Tierra + Amonitas Carbono=Fuego + Madera Cloro=Fuego + Sal + Electricidad CO2(Dióxido de Carbono)=Ceniza + Ácido nítrico Electricidad=Relámpago+ Metales Gas natural= Yacimiento de gas + Pozo Helio=Refinería de gas + Gas Natural Hidrógeno=Electricidad + Agua Hielo=Frío + Agua Imán=Piedra + Metales Metano=Deshechos Vegetales + Pantano Oxígeno=Electricidad + Agua Pe...

Soluciones Alchemy Classic 442 elementos

Después de la resaca navideña y de la cuesta de enero, volvemos para informar la agradable sorpresa que nos ha dado a los fans de Alchemy Classic la empresa NIASOF ,  tras actualizar el juego Alchemy Classic. Una nueva versión con 442 elementos , interfaz mejorada de grupos y lo más importante, nuevos elementos que descubrir. La gran novedad de esta actualización son los puntos que tienes asignados , con los que puedes  conseguir pistas sobre los elementos que no has abierto todavía como: Conseguir un subelemento de un elemento, con 100 puntos . Conseguir el grupo de un subelemento de un elemento (qué lío , jeje), con 35 puntos . Me gusta, me gusta el enfoque de esta nueva versión aunque los elementos que han sacado me parecen poco originales. Parece que se van agotando las ideas para los elementos nuevos. Aquí van las soluciones: Carbon = Tierra + Turba Sol = Estrella + Tierra Espacio = 3 x Estrella Estrella = Helio + Hidrógeno Oso Pa...

Alchemy Classic

Dentro de los juegos que he descargado con el Android Market hay dos que destacan sobre todos los demás. El primero es Angry Birds (pájaros furiosos), en el cual comandas un ejercito de pájaros para luchar con una piara de cerdos malotes los cuales han robado sus huevos. Básicamente, tienes que ir pasando nivel tras nivel afinando tu puntería lanzando los pájaros a los cerdos que se esconden tras maderas, cristales y piedras. Pero la verdadera joya que no es tan conocida es Alchemy Classic . En este juego puedes desempeñar el papel  de Dios. Comenzando con 4 elementos básicos, Aire, Agua, Tierra y Fuego tienes que ir obteniendo todos los demás elementos de la creación (animales, plantas, herramientas...) mediante la combinación de los 4 primeros. En total hay 238 elementos. Después de una semana y alguna noche sin dormir por fin los he obtenido todos. El último elemento me ha llevado más de la cuenta por su maravillosa traducción. ¿Qué son abrojos? ¿Lo sabéis? Pues no, ...