Alters

Hacer DELTA con java

Buenas!

¡Dos entradas en dos días! ¿Qué puedo decir?...

Creo que este fin de semana estoy inspirado, y eso es bueno :D
En fin, vamos a pasar a otro temilla rápido que nos ocupará una nueva entrada en este blog... y es el trabajar con DELTA en java.

¡Vamos allá!




Para empezar... ¿qué es un DELTA?

Concepto de DELTA


Pues no me refiero a un ala delta (ba dum tss), si no a un método de comparación de datos que se basa en encontrar los cambios entre dos versiones del mismo conjunto de datos.

¿Nani?

Con un ejemplo se ve mejor (aunque ya sabéis que mis ejemplos suelen ser malísimos, pero me esfuerzo).

Supongamos durante lo que dura esta entrada, que somos los gerentes de una empresa, y queremos ver los cambios que se generan en nuestra plantilla mes a mes (nuestra empresa es pequeña y no tiene sentido mirar los cambios a diario).

Podríamos tener, por ejemplo, la siguiente plantilla

Mes de Enero:


  • Empleado 1
    • Nombre: David
    • Puesto: Gerente
    • Horas semanales: 100
  • Empleado 2
    • Nombre: Oscar
    • Puesto: Encargado de la limpieza
    • Horas semanales: 40
  • Empleado 3
    • Nombre: Solé
    • Puesto: Programador Junior
    • Horas semanales: 20
  • Empleado 5
    • Nombre: González
    • Puesto Programador Senior
    • Horas semanales: 50


Mes de Febrero:


  • Empleado 1
    • Nombre: David
    • Puesto: Gerente
    • Horas semanales: 90
  • Empleado 2
    • Nombre: Oscar
    • Puesto: Encargado de la limpieza
    • Horas semanales: 40
  • Empleado 5
    • Nombre: González
    • Puesto: Analista
    • Horas semanales: 50
  • Empleado 4
    • Nombre: Solé Jr.
    • Puesto: Programador Junior
    • Horas Semanales: 20

Bien, de aquí vamos a sacar unos cuantos conceptos...

  1. Todos los empleados de la plantilla se consideran un conjunto de datos
  2. El conjunto de datos de enero lo llamaremos "antiguo"
  3. El conjunto de datos de febrero lo llamaremos "actual"

Si comparamos "antiguo" y "actual", observamos

  1. David ha reducido su horario (aunque no vamos a tenerlo en cuenta para el DELTA)
  2. González ha cambiado de puesto
  3. Solé ha dejado la empresa
  4. Solé Jr. se ha incorporado a la empresa

Estos cuatro cambios, sería el resultado de aplicar un DELTA a "actual" sobre "antiguo".


Realizando un DELTA en Java

Bien, si aplicamos todo esto a Java, es relativamente sencillo tener un mapa mental claro... cada empleado sería una instancia de una nueva clase Empleado, y simplemente bastaría con hacer un par de operaciones con los conjuntos de datos para obtener los objetos que se han mantenido, y los que han cambiado (y dentro de estos últimos, cuales se han añadido - insert -, cuales han cambiado - update - y cuales se han eliminado - delete -).


Separando los datos comunes

Esta parte es sencilla. Básicamente tomamos "antiguo", y lo recorremos de arriba a abajo. Por cada elemento de "antiguo", miramos si existe en "actual".

Si existe, es un dato común; si no, es un dato no común, y lo almacenamos a parte.

Para los datos no comunes, ya tenemos una parte hecha en el funcionamiento descrito en los párrafos anteriores; lo que nos queda por hacer ahora es hacer la inversa: recorrer "actual" y comparar con "actual", pero solo quedándonos con los datos no comunes (los comunes ya los tenemos localizados).

En este punto, la comparación la debemos hacer de un modo completo... es decir, buscamos cualquier tipo de cambio relevante (cambios en el número de empleado, en el nombre, o en el puesto, pero no en las horas).

Con esto, deberíamos tener tres subconjuntos de datos

  1. Datos que solo existen en "antiguo" (alpha)
  2. Datos que solo existen en "nuevo" (beta)
  3. Datos comunes

Tipología del cambio


Ahora, haremos una comparación similar a la anterior, pero con "alpha" y "beta", y esta vez solo compararemos los datos clave (número de empleado).

Tras esta comparativa, tendremos otros tres subconjuntos de datos

  1. Datos que solo existen en "alpha" (no existen en "beta", por tanto, se han borrado)
  2. Datos que solo existen en "beta" (no existen en "alpha", por tanto, se han añadido)
  3. Datos que existen en "alpha" y "beta" (existen en ambos, por tanto, se han modificado).

Todo este procedimiento lo podemos encapsular en una clase, pero previamente hay que establecer unos requerimientos.


Clase base


Necesitamos garantizar que la clase sobre la que hagamos el DELTA tenga un método que compare un objeto dado con la propia instancia, y que además, podamos indicar si queremos hacer la comparación de todos los datos, o únicamente de los datos clave.

Para ello, crearemos una clase abstracta llamada "Historify", en la que fijaremos nuestros requerimientos.


[code lan=gral] /** * Historify Class. * * Base Class to be used on Delta Class. It provides the basic methods to be * used on Delta. * * @author DoHITB * */ public abstract class Historify{ /** * Object Id. It identifies each class instance */ private Object id; /** * Class Constructor. * * It checks that "id" cannot be null. * @param id: instance ID * @throws NullIdException if id attribute is null */ public Historify(Object id) throws NullIdException{ if(id == null) throw new NullIdException("Invalid ID provided"); this.id = id; } /** * Equals method. * * It compares current instance against given object. Both objects must * have same class to be compared. * * @param o: object to compare against * @param strict: if true, full comparation will be done (hardEquals) * @return true if equals; false otherwise * @throws ClassDifferException if o class differs from current class. */ public final boolean equals(Historify o, boolean strict) throws ClassDifferException { if(!o.getClass().equals(this.getClass())) throw new ClassDifferException(this, o); if(strict) return this.hardEquals(o); return this.id.equals(o.getId()); } /** * HardEquals method. * * It makes a full comparation of all attributes on the class. * @param o: object to compare against * @return true if equals; false otherwise */ public abstract boolean hardEquals(Historify o); /** * GetId method. * * Getter for "id" attribute * @return id */ public Object getId() { return this.id; } } [/code]

Con esta clase, podemos asegurar que todos las clases que hereden "Historify" tendrán los mecanismos que necesitamos.

Ahora, vamos a pasar a implementar el DELTA.


Clase DELTA

Para esta clase, vamos a usar lo que se conoce como tipo genérico. Es decir, sabemos que vamos a estar trabajando con una subclase de "Historify" (ya que esta clase es abstracta y no se puede instanciar), así que no podemos usar una clase en concreto... esto se soluciona, efectivamente, con un tipo genérico.


[code lan=gral] import java.util.ArrayList; /** * Delta Class * * Helper class that is suitable to do delta comparation between two data array * * The data array must have same Class, either it would not be able to compare * Also, the Class must extend the Historify Class, as it have comparation * methods declared that are being used on this class: * * - equals: this is a final boolean method, so there is no need to implement * it on subclasses. It does a compare, but only having in mind the * ID's. * * - hardEquals: this method must be implemented on each subclass. It may * do a compare of all attributes in class. * * @author DoHITB * * @param : The Class is being used. */ public class Delta <T extends historify>{ private T[] old; private T[] cur; private ArrayList<T> ins; private ArrayList<T> upd; private ArrayList<T> del; private ArrayList<T> alpha; private ArrayList<T> aandb; private ArrayList<T> beta; /** * Class constructor * * @param old: an array of <T> Class having the previous data * @param cur: an array of <T> Class having the current data */ public Delta(T[] old, T[] cur) { this.old = old; this.cur = cur; } /** * Main method for Delta Class. * * It distributes data, then do the Delta. * After this execution, "ins", "upd", and "del" will be fulfilled. * * @throws ClassDifferException if "old" and "cur" have different class. */ public void check() throws ClassDifferException{ //restart all the data init(); //first, distribute all the data. this.distribute(); //then, do the delta this.doDelta(); } /** * Distribute method. * * Based on "old" and "cur", it distributes which data are on both sides, * and which one is only on one of the data arrays * * @throws ClassDifferException if "old" and "cur" have different class. */ private void distribute() throws ClassDifferException{ //first, check old vs cur this.compare(this.old, this.cur, true, this.aandb, this.alpha, true); //then, check for cur vs old this.compare(this.cur, this.old, false, null, this.beta, true); } @SuppressWarnings("unchecked") /** * doDelta method. * * Based on the results of "distribute", it distributes which data are on * both sides, and which one is only on one of the data arrays * * @throws ClassDifferException if "old" and "cur" have different class. */ private void doDelta() throws ClassDifferException{ T[] alphaA = (T[]) new Historify[this.alpha.size()]; T[] betaA = (T[]) new Historify[this.beta.size()]; alphaA = this.alpha.toArray(alphaA); betaA = this.beta.toArray(betaA); //finally, check alpha vs beta, and beta vs alpha this.compare(alphaA, betaA, true, this.upd, this.del, false); this.compare(betaA, alphaA, false, null, this.ins, false); } /** * Compare method. * * It checks if given array exists on the given array. If "storeEquals" is * true, it will store all data existing on the given array. If "strict" is * true, it will do a full comparation of the object against the data array * * @param a: array to be checked object by object * @param b: array to be checked against * @param storeEquals: if true, positive results will be stored * @param equal: ArrayList on which positive results will be stored * @param differ: ArrayList on which negative results will be stored * @param strict: if true, a full comparation will be done. * @throws ClassDifferException if "old" and "cur" have different class. */ private void compare(T[] a, T[] b, boolean storeEquals, ArrayList<T> equal, ArrayList<T> differ, boolean strict) throws ClassDifferException{ for(T obj : a) if(this.equals(obj, b, strict)) //the Object exist both in old and cur this.storeEquals(storeEquals, equal, obj); else //the Object only exist in old differ.add(obj); } /** * Equals method. * * It checks if given object exists on the given array. If "strict" is true * it will do a full compare; else it will only compare both ID's. * * @param o: object to be checked against array * @param a: array to be checked against * @param strict: if true, it will do a full comparation * @return true if found, else otherwise * @throws ClassDifferException if "old" and "cur" have different class. */ private boolean equals(T o, T[] a, boolean strict) throws ClassDifferException{ for(T obj : a) if(obj.equals(o, strict)) return true; return false; } /** * StoreEquals method. * * It checks if positive results may be stored, then store if necessary * * @param done: if true, results will be stored * @param target: ArrayList to be increased * @param item: Object to be stored */ private void storeEquals(boolean done, ArrayList<T> target, T item) { if(done) target.add(item); } /** * Init method. * * It initializes all the ArrayLists */ private void init() { this.aandb = new ArrayList<T>(); this.alpha = new ArrayList<T>(); this.beta = new ArrayList<T>(); this.del = new ArrayList<T>(); this.ins = new ArrayList<T>(); this.upd = new ArrayList<T>(); } /** * getInserted method. * * Getter method ins attribute * @return ArrayList<T> ins */ public ArrayList<T> getInserted(){ return this.ins; } /** * getUpdated method. * * Getter method upd attribute * @return ArrayList<T> upd */ public ArrayList<T> getUpdated(){ return this.upd; } /** * getDeleted method. * * Getter method del attribute * @return ArrayList<T> del */ public ArrayList<T> getDeleted(){ return this.del; } } [/code]

En este caso, el tipo genérico es <T extends Historify>, que nos permite referirnos a "T" dentro del código como un tipo definido de clase, asegurando que será una subclase de "Historify".

Y con esto, tendríamos una implementación de un sistema DELTA en java!

Espero que os sea útil, o que al menos os sirva para ver nuevos conceptos!! :D

Os dejo el enlace al proyecto en GitHub, por si queréis verlo mejor.


Hasta la próxima!

No hay comentarios:

Publicar un comentario