Construcción de Árboles Jerárquicos mediante Anotaciones en Java

En el desarrollo de software, es común necesitar estructuras de árbol para representar jerarquías. Este artículo demuestra cómo generar dichas estructuras a partir de colecciones planas usando anotaciones personalizadas en Java.

Definición de Anotaciones

Se crean anotaciones para marcar campos que identifican nodos y sus relaciones jerárquicas.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IdentificadorNodo {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface IdentificadorPadre {
}

Modelo de Nodo

Una clase que representa un nodo del árbol, con campos anotados para los identificadores y una lista para los hijos.

public class Departamento {
    @IdentificadorNodo
    private String codigo;

    @IdentificadorPadre
    private String codigoPadre;

    private String nombre;
    private List<Departamento> subordinados = new ArrayList<>();

    // Getters y setters omitidos por brevedad
}

Utilidad de Construcción

Una herramienta genérica que utiliza reflexión para construir el árbol basándose en las anotaciones.

public class GeneradorArbol<T> {
    public List<T> generar(List<T> elementos) {
        if (elementos == null || elementos.isEmpty()) {
            return Collections.emptyList();
        }

        Class<?> tipo = elementos.get(0).getClass();
        Campo campoId = obtenerCampoAnotado(tipo, IdentificadorNodo.class);
        Campo campoPadre = obtenerCampoAnotado(tipo, IdentificadorPadre.class);
        Campo campoHijos = buscarCampoHijos(tipo);

        establecerAccesibilidad(campoId, campoPadre, campoHijos);

        Map<Object, T> mapa = new HashMap<>();
        for (T elem : elementos) {
            Object id = campoId.get(elem);
            mapa.put(id, elem);
        }

        List<T> raices = new ArrayList<>();
        for (T elem : elementos) {
            Object padreId = campoPadre.get(elem);
            T padre = mapa.get(padreId);

            if (padre == null || "".equals(padreId)) {
                raices.add(elem);
            } else if (campoHijos != null) {
                List<T> hijos = (List<T>) campoHijos.get(padre);
                if (hijos == null) {
                    hijos = new ArrayList<>();
                    campoHijos.set(padre, hijos);
                }
                hijos.add(elem);
            }
        }
        return raices;
    }

    private Campo obtenerCampoAnotado(Class<?> tipo, Class<? extends java.lang.annotation.Annotation> anotacion) {
        for (Campo campo : tipo.getDeclaredFields()) {
            if (campo.isAnnotationPresent(anotacion)) {
                return campo;
            }
        }
        throw new IllegalArgumentException("Anotación no encontrada: " + anotacion);
    }

    private Campo buscarCampoHijos(Class<?> tipo) {
        try {
            return tipo.getDeclaredField("subordinados");
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

    private void establecerAccesibilidad(Campo... campos) {
        for (Campo campo : campos) {
            if (campo != null) {
                campo.setAccessible(true);
            }
        }
    }
}

// Ejemplo de uso con un identificador raíz específico
public List<Departamento> construirDesdeColeccion(List<Departamento> datos, String idRaiz) {
    Map<String, Departamento> indice = new HashMap<>();
    for (Departamento dept : datos) {
        indice.put(dept.getCodigo(), dept);
    }

    List<Departamento> raices = new ArrayList<>();
    for (Departamento dept : datos) {
        if (idRaiz.equals(dept.getCodigoPadre())) {
            raices.add(dept);
        } else {
            Departamento padre = indice.get(dept.getCodigoPadre());
            if (padre != null) {
                padre.getSubordinados().add(dept);
            } else {
                dept.setCodigoPadre(idRaiz);
                raices.add(dept);
            }
        }
    }
    return raices;
}

Recorrido Jerárquico

Un método para recorrer el árbol desde la raíz hacia los niveles inferiores usando una cola.

public void recorrerEnAmplitud(Departamento raiz) {
    if (raiz == null) return;
    Cola<Departamento> cola = new LinkedList<>();
    cola.offer(raiz);
    while (!cola.isEmpty()) {
        Departamento actual = cola.poll();
        System.out.println(actual.getNombre());
        cola.addAll(actual.getSubordinados());
    }
}

Etiquetas: java anotaciones reflexión estructuras de datos árboles

Publicado el 7-9 22:49