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());
}
}