📚 Chuleta M0486 · Acceso a Datos — Teoría + práctica media-manager

0 El módulo M0486 · Acceso a Datos

Gestionar datos = almacenar de forma persistente y segura, recuperar eficientemente, modificar manteniendo la integridad, eliminar datos obsoletos y convertir entre formatos.

Objetivo: aplicar técnicas de persistencia (ficheros, BD relacionales y no relacionales) con una separación clara entre lógica de negocio y capa de persistencia.

RAContenido
RA1Ficheros: directorios y rutas, ficheros de texto y binarios, secuenciales y de acceso directo, serialización y tratamiento de excepciones.
RA2BD relacionales con conectores (JDBC): crear/modificar esquemas, sentencias SQL, transacciones, procedimientos almacenados.
RA3ORM (Hibernate / JPA): mapeo objetos↔tablas, clases persistentes, anotaciones o ficheros de configuración, CRUD y consultas avanzadas con transacciones.
RA4BD objeto-relacionales y orientadas a objetos.
RA5BD documentales nativas (NoSQL): info flexible y semiestructurada, colecciones, conexiones, documentos y consultas.
RA6Componentes de acceso a datos (transversal): componentes reutilizables que encapsulan el acceso a datos, aplicando patrones de diseño.
Relación con otros módulosMuy relacionado con Programación y Multimedia/Móviles, pero sobre todo con Desarrollo de Interfaces: Acceso a Datos = parte interna (BD, consultas, datos correctos); Interfaces = parte visible. Juntos: datos ↔ lógica ↔ interfaz.

0.1 Arquitectura de aplicaciones

🎨 Front end (interfaz)

  • Parte visible e interactiva.
  • Muestra datos del back end, recoge acciones (clics, formularios, gestos), validación básica (campo no vacío).
  • Elementos: pantallas, formularios, botones, menús.
  • Objetivo: buena UX. Ej.: pantalla Flutter con formulario de alta.

⚙️ Back end (datos y lógica)

  • Parte interna: lógica de negocio + persistencia.
  • Procesa peticiones, consulta/modifica la BD, aplica reglas de negocio (validaciones, permisos, cálculos), responde en JSON o XML.
  • Elementos: conexiones BD, sesiones, seguridad.
  • Ej.: en Java comprobar usuario/contraseña y devolver productos en JSON.

Comunicación: API

  • API = puerta de entrada al back end, idioma común. Protocolo HTTP/HTTPS.
  • Verbos: GET recuperar · POST crear/enviar nuevo · PUT/PATCH actualizar · DELETE eliminar.
  • Formato normal: JSON (ligero, legible). Seguridad: tokens (JWT) o claves. Rendimiento: cachés. Documentación: OpenAPI/Swagger. El front trabaja de forma asíncrona (no bloquea la UI).
Usuario pulsa "Mostrar productos"→GET /productes→Back end consulta BD + lógica→Respuesta JSON→Front lo pinta (tarjetas)

1 Acceso a datos en ficheros · java.io vs java.nio

Las apps guardan información persistente en ficheros o bases de datos. En Java hay dos grandes familias de APIs.

java.io (clásica)

  • Desde las primeras versiones.
  • Orientada a flujos (streams): byte a byte o carácter a carácter. Control de bajo nivel.
  • Cada operación es bloqueante (el hilo se para hasta que acaba).
  • Sencilla; buena para ficheros pequeños y operaciones secuenciales.
  • Menos eficiente con grandes volúmenes o muchos canales (servidores).

java.nio (New I/O) — Java 1.4

  • Orientada a buffers y canales: lee en bloques.
  • Puede ser no bloqueante.
  • Métodos directos: Files.copy, Files.readAllLines, Files.write.
  • Mejor gestión de directorios y metadatos (permisos, symbolic links…).
  • Ideal para ficheros grandes y muchas conexiones (sockets, chats, juegos online).
¿Cuál usar?java.io: gestión simple y secuencial de ficheros pequeños, entornos antiguos/educativos. java.nio: eficiencia y escalabilidad (red, ficheros grandes) y proyectos nuevos donde se quiere código corto (copiar, mover, leer todo, escribir listas de líneas). En clase empezamos con java.io.

La clase java.io.File

EXAMENFile NO representa el contenido del fichero, sino una abstracción de su ruta. Sirve para ficheros y directorios: comprobar existencia, crear/borrar, consultar propiedades (tamaño, permisos, fecha), recorrer directorios… pero no leer ni escribir. Crear un objeto File no comprueba si existe → hay que llamar a exists().

Constructores

File f1 = new File("dades.txt");                 // 1) File(String pathname) – ruta absoluta o relativa
File f2 = new File("resources", "dades.txt");    // 2) File(String parent, String child)
File carpeta = new File("resources");
File f3 = new File(carpeta, "dades.txt");        // 3) File(File parent, String child)

Tipos de path

PathQué esMétodo
RelativoRespecto al directorio de trabajo actual (working directory).getPath() (tal cual se pasó)
AbsolutoRuta completa, no depende de dónde se ejecuta.getAbsolutePath()
CanónicoVersión limpia y única: elimina . y .. y resuelve enlaces simbólicos. Lanza IOException.getCanonicalPath()
File fitxer = new File("resources/../resources/dades.txt");
fitxer.getPath();          // resources/../resources/dades.txt  (tal cual)
fitxer.getAbsolutePath();  // /home/user/proj/resources/../resources/dades.txt
fitxer.getCanonicalPath(); // /home/user/proj/resources/dades.txt  (limpio) → throws IOException
fitxer.getName();          // dades.txt
fitxer.getParent();        // resources/../resources
fitxer.toString();         // = getPath()

Métodos más habituales

MétodoDevuelve / hace
exists()boolean: ¿existe el fichero/directorio?
createNewFile()Crea el fichero vacío; true si lo ha creado (false si ya existía). Lanza IOException.
delete()Borra fichero o directorio (el directorio debe estar vacío).
mkdir() / mkdirs()Crea un directorio / crea el directorio y todos los padres que falten.
isFile() / isDirectory()¿Es fichero? / ¿Es directorio?
length()Tamaño en bytes (solo tiene sentido en ficheros).
getName(), getParent(), getAbsolutePath(), getCanonicalPath()Nombre, directorio padre, rutas.
canRead(), canWrite(), canExecute()Permisos.
lastModified()Fecha última modificación (long, milisegundos).
renameTo(File dest)Renombra/mueve.
list() / listFiles()Contenido del directorio: String[] de nombres / File[].
toPath()Convierte a java.nio.file.Path (puente a NIO).
Ejemplo FileFile fitxer = new File("dades.txt");
if (fitxer.exists()) {
    System.out.println("Ruta absoluta: " + fitxer.getAbsolutePath());
    System.out.println("És un directori? " + fitxer.isDirectory());
    System.out.println("Mida en bytes: " + fitxer.length());
} else {
    System.out.println("El fitxer no existeix.");
}

Excepciones: detección y tratamiento

Una excepción es un evento anómalo que interrumpe el flujo normal. Se lanza (throw) un objeto que deriva de Throwable. El método se para y la JVM busca un catch compatible en ese método; si no hay, sube al método llamador… recorriendo la pila de llamadas (call stack) hasta main. Si nadie la captura → el programa termina y muestra el stack trace.

ExcepciónCuándo
FileNotFoundExceptionEl fichero no existe o no hay permisos.
IOExceptionError genérico de E/S (disco lleno, error de lectura…). Es la "madre" de las anteriores.
EOFExceptionFin de fichero alcanzado de forma inesperada.
SecurityExceptionPermisos denegados.
NoSuchFileException, FileAlreadyExistsExceptionNIO.2, más específicas (copiar/mover).
EXAMENLas excepciones de ficheros son checked → el compilador obliga a capturar (try-catch) o propagar (throws).

Capturar (try-catch)

Cuando el método tiene información suficiente para resolverlo. Ej.: falta el fichero de configuración → cargar configuración por defecto; fichero de salida ocupado → pedir otro nombre. El programa sigue.

Propagar (throws)

Cuando el método no sabe qué hacer o le falta contexto. Ej.: un método de "capa baja" llegirFitxer(); una biblioteca/API → que decida la app cliente.

Gestión automática de recursos: try-with-resources

  • La memoria de objetos normales la libera el Garbage Collector (no hay free/delete como en C).
  • Los recursos externos (ficheros, streams, conexiones de red/BD) NO los libera el GC → hay que cerrarlos con close() o usar try-with-resources.
  • Java 7. Los recursos se declaran entre paréntesis tras try y se cierran automáticamente (haya excepción o no) en orden inverso al de declaración. Evita resource leaks. El recurso debe implementar AutoCloseable.
try (BufferedReader br = new BufferedReader(new FileReader("in.txt"));   // se cierra 2º
     BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"))) { // se cierra 1º
    // ...
} catch (IOException e) {
    e.printStackTrace();
}

2 Tipos de acceso a ficheros

Tres enfoques: secuencial, aleatorio/directo y secuencial con buffers. La forma de acceder impacta en rendimiento y complejidad.

➡️ Secuencial

Del principio al final, línea a línea o byte a byte. Cada lectura avanza el puntero interno; no se puede saltar sin recorrer lo anterior.

  • Simple
  • Ideal para procesar ficheros completos (texto, informes, logs)
  • Pocos errores (lógica lineal)
  • Poco flexible: para la línea 5000 lees las 4999 anteriores
  • Lento en ficheros grandes si solo quieres fragmentos

🎯 Directo / aleatorio

Puntero colocable en cualquier byte. En Java: RandomAccessFile.

  • Acceso muy rápido a cualquier posición
  • Ideal con registros de tamaño fijo (p.ej. 100 bytes)
  • Modificar/añadir sin reprocesar todo
  • Más complejo: controlar posiciones y tamaños
  • Mal con datos de tamaño variable (líneas de texto)
  • Errores si se gestiona mal el puntero

📦 Secuencial con buffers

Optimización del secuencial: lee/escribe bloques grandes en un buffer en memoria que hace de intermediario.

  • Mucho más eficiente: menos accesos físicos a disco
  • Mejor rendimiento en ficheros grandes
  • Útil en procesamiento intensivo
  • Sigue siendo secuencial (no salta)
  • Algo más de complejidad / memoria intermedia

3 Lectura y escritura secuencial (streams)

Un stream es una secuencia ordenada de datos que circula de un origen (fichero, memoria, red…) a un destino (fichero, pantalla, socket…). Permite tratar la E/S de forma uniforme sea cual sea el dispositivo.

Flujos de bytesFlujos de caracteresFlujos con buffer
DatosBinarios, unidades de 8 bits (0-255), sin interpretarTexto, conversión automática bytes→caracteres según codificación (UTF-8, ISO-8859-1)Añaden un buffer en RAM a cualquiera de los anteriores
UsoImágenes, audio, ZIP.txt, .csv, .xml…Siempre que se pueda (eficiencia)
LecturaInputStream (abstracta) → FileInputStreamReader (base) → FileReaderBufferedInputStream · BufferedReader
EscrituraOutputStream (abstracta) → FileOutputStreamWriter (base) → FileWriterBufferedOutputStream · BufferedWriter
¿Por qué buffers?Byte a byte es lento porque cada operación accede al disco. El buffer lee/escribe un bloque completo en RAM y luego se procesa desde ahí → menos operaciones de E/S y métodos de alto nivel como readLine().
Bytes (copia byte a byte)FileInputStream in = new FileInputStream("entrada.bin");
FileOutputStream out = new FileOutputStream("sortida.bin");
int byteLlegit;
while ((byteLlegit = in.read()) != -1) { // -1 = fin
    out.write(byteLlegit);
}
in.close();
out.close();
Caracteres (copia carácter a carácter)FileReader in = new FileReader("entrada.txt");
FileWriter out = new FileWriter("sortida.txt");
int caracter;
while ((caracter = in.read()) != -1) {
    out.write(caracter);
}
in.close();
out.close();
Buffered + try-with-resources (numera líneas)try (BufferedReader br = new BufferedReader(new FileReader("entrada.txt"));
     BufferedWriter bw = new BufferedWriter(new FileWriter("sortida.txt"))) {
    String linia;
    int numLinia = 1;
    while ((linia = br.readLine()) != null) {   // readLine devuelve null al final
        bw.write(numLinia + ": " + linia);
        bw.newLine();                           // salto de línea
        numLinia++;
    }
} catch (IOException e) {
    e.printStackTrace();
}
OJOread() devuelve int y -1 al final del fichero · readLine() devuelve String y null al final. Para añadir al final sin sobrescribir: new FileWriter("f.txt", true).
Buenas prácticasCerrar siempre los flujos (close()) · usar try-with-resources (Java 7+) · usar buffers siempre que sea posible.

4 Acceso directo / aleatorio · RandomAccessFile

Situarse en cualquier posición física del fichero para leer/escribir sin recorrerlo. Imprescindible: registros de tamaño fijo y conocido → se calcula dónde empieza cada registro.

  • Un fichero es una secuencia de bytes; cada byte tiene un índice desde 0 (offset).
  • Un puntero de fichero (file pointer) indica la posición actual; se reposiciona con seek.
  • Casos de uso: registros fijos con acceso rápido y actualización sin reescribir todo, sistemas antiguos, mini-BD / ejercicios académicos.

RandomAccessFile (paquete java.io)

  • Modo "r" (solo lectura → el fichero debe existir, si no FileNotFoundException) o "rw" (lectura/escritura → si no existe lo crea).
  • Puntero inicia en 0; cada read/write lo avanza los bytes leídos/escritos.
Control del puntero
getFilePointer()Posición actual (long).
seek(long pos)Mueve a posición absoluta desde el inicio.
length()Tamaño total en bytes (marca el final).
skipBytes(int n)Desplaza n bytes desde la posición actual.
RandomAccessFile raf = new RandomAccessFile("dades.dat", "rw");
long pos = raf.getFilePointer();
raf.seek(100);            // salta al byte 100
raf.seek(0);              // al principio
raf.seek(raf.length());   // al final (para AÑADIR)
raf.seek(pos - 10);       // retroceder 10 bytes
raf.skipBytes(50);        // avanza 50 bytes

Tamaño de los tipos primitivos MEMORIZAR

booleanbytecharshortintlongfloatdouble
112 (UTF-16)2484 (IEEE 754)8 (IEEE 754)

Métodos: read()/write(int b) (1 byte), readBoolean/writeBoolean, readChar/writeChar, readInt/writeInt, readDouble/writeDouble, etc. Lectura avanza el puntero. Escritura: si el puntero está en medio sobrescribe; si está al final añade.

Ejemplo: mini-BD de Alumnos

Campos: id (int) · nom (15 chars) · cognom (30 chars) · edat (int) · notaMitjana (double)

Cálculo del tamaño de registro id 4 + nom 15×2=30 + cognom 30×2=60 + edat 4 + nota 8 = 106 bytes
Posición del registro con id (empezando en 1): (id - 1) * MIDA_REGISTRE · Nº registros: raf.length() / MIDA_REGISTRE · Nuevo id: length()/MIDA_REGISTRE + 1
GestorAlumnes (resumen del ejemplo)private static final int MIDA_NOM_CHAR = 15, MIDA_COGNOM_CHAR = 30, MIDA_CHAR = 2;
private static final int MIDA_REGISTRE = 4 + MIDA_NOM_CHAR*MIDA_CHAR + MIDA_COGNOM_CHAR*MIDA_CHAR + 4 + 8; // 106

// AÑADIR
public static void afegirAlumne(Alumne a) throws IOException {
    File fitxer = new File(NOM_FITXER);
    File carpeta = fitxer.getParentFile();
    if (carpeta != null && !carpeta.exists()) carpeta.mkdirs();
    try (RandomAccessFile bd = new RandomAccessFile(NOM_FITXER, "rw")) {
        int id = (int) (bd.length() / MIDA_REGISTRE) + 1;   // id automático, evita el 0
        bd.seek(bd.length());                               // ir al final
        bd.writeInt(id);
        escriureStringFix(bd, a.getNom(), MIDA_NOM_CHAR);
        escriureStringFix(bd, a.getCognom(), MIDA_COGNOM_CHAR);
        bd.writeInt(a.getEdat());
        bd.writeDouble(a.getNotaMitjana());
    }
}

// LEER POR ID
public static Alumne llegirAlumne(int id) throws IOException {
    try (RandomAccessFile bd = new RandomAccessFile(NOM_FITXER, "r")) {
        long posicio = (long)(id - 1) * MIDA_REGISTRE;
        if (posicio >= bd.length()) return null;           // no existe
        bd.seek(posicio);
        int idL = bd.readInt();                             // MISMO ORDEN que al escribir
        String nom = llegirStringFix(bd, MIDA_NOM_CHAR);
        String cognom = llegirStringFix(bd, MIDA_COGNOM_CHAR);
        int edat = bd.readInt();
        double nota = bd.readDouble();
        return new Alumne(idL, nom, cognom, edat, nota);
    }
}

// LISTAR TODOS: for (i = 0; i < bd.length()/MIDA_REGISTRE; i++) { bd.seek(i*MIDA_REGISTRE); ...leer... }

// Strings de tamaño fijo: rellenar con espacios / trim al leer
private static void escriureStringFix(RandomAccessFile raf, String text, int mida) throws IOException {
    for (int i = 0; i < mida; i++) {
        char c = (i < text.length()) ? text.charAt(i) : ' ';
        raf.writeChar(c);
    }
}
private static String llegirStringFix(RandomAccessFile raf, int mida) throws IOException {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < mida; i++) sb.append(raf.readChar());
    return sb.toString().trim();
}

Baja lógica vs baja física EXAMEN

✅ Baja lógica (recomendada)

El registro se queda en el fichero pero con un campo indicador (boolean actiu, o char/int especial) marcado como borrado. Las consultas filtran por activos.

  • Muy eficiente (un seek + una escritura)
  • No rompe el tamaño fijo
  • Recuperable (volver a activo)
  • El fichero crece con inactivos → compactar de vez en cuando (fichero nuevo solo con activos)

Baja física

Mover los registros posteriores hacia delante para llenar el hueco. Implica leer y reescribir muchos registros → poco eficiente. Solo si los ficheros crecen demasiado o no se pueden usar bajas lógicas.

Buenas prácticas
  • Formato binario, no legible con editor de texto.
  • Planificar registros de tamaño fijo → posición = índice × tamaño.
  • Tener en cuenta los bytes de cada tipo.
  • Misma secuencia de métodos al leer y escribir, si no → valores incorrectos o excepciones.
  • Cada lectura/escritura mueve el puntero automáticamente.
  • Escribir en medio sobrescribe; para añadir → seek(length()).
  • Usar bajas lógicas.

5 Serialización y deserialización de objetos

Serializar = convertir un objeto (y todos los que contiene/referencia) en una secuencia de bytes (fichero, red, BD). Deserializar = reconstruir el objeto con el mismo estado.

Usos: guardar/recuperar objetos fácilmente, enviarlos entre programas Java por red (cliente-servidor), replicar estado en frameworks/servidores (Tomcat serializa sesiones, cachés distribuidas). Herramienta para la eficiencia interna en entornos Java.

Funcionamiento

  • La clase debe implementar Serializable (interfaz marcadora). Escritura con ObjectOutputStream (writeObject), lectura con ObjectInputStream (readObject + cast).
  • Si una clase es serializable, sus hijas también.
  • serialVersionUID: identifica la versión de la clase; si cambia la definición y no coincide → InvalidClassException. Recomendable definirlo explícitamente.
  • transient: atributos que no se guardan (sensibles o derivables) → al deserializar valen null/0.
  • Atributos static no se serializan (son de la clase, no de la instancia).
LimitacionesFormato binario y específico de Java (no legible ni portable) · frágil ante cambios de clase · riesgo de seguridad: deserializar datos no fiables puede ejecutar código malicioso. Evitar para: intercambio con otros lenguajes (→ JSON/XML), persistencia a largo plazo (→ BD o formatos textuales), datos de usuarios desconocidos.
Ejemplo serializaciónclass Usuari implements Serializable {
    private static final long serialVersionUID = 1L;
    private String email;
    private transient String password;   // NO se guarda → al leer será null
    // constructor, getters...
}

// Serializar
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("usuari.dat"))) {
    oos.writeObject(usuari);
} catch (IOException e) { e.printStackTrace(); }

// Deserializar
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("usuari.dat"))) {
    Usuari u = (Usuari) ois.readObject();          // cast obligatorio
} catch (IOException | ClassNotFoundException e) { // ClassNotFoundException también
    e.printStackTrace();
}

6 Intercambio de datos: formatos

Cuando aplicaciones/servicios/módulos se comunican necesitan un lenguaje común: los formatos de intercambio.

CSVJSONXML
Qué esTexto plano; cada línea un registro, campos separados por , (o ; \t)Texto ligero con objetos (clave/valor) y arraysBasado en etiquetas con jerarquía
UsoDatos tabulares: BD ↔ Excel, ETL, informesAPIs REST, configuración, datos jerárquicos, web y móvilSistemas empresariales, servicios web antiguos (SOAP), configuración, legacy
VentajasMuy sencillo y legible, compatible con ofimática, ficheros pequeños y rápidosLegible, estructuras complejas, librerías (Jackson, Gson), más compacto que XMLJerarquía clara, validación con XSD o DTD, namespaces, comentarios, atributos, binding con JAXB
InconvenientesSin estándar único (delimitadores, comillas, codificación), solo tablas planas, sin validación ni tiposSin esquema obligatorio (opcional JSON Schema), no permite comentarios, ineficiente en volúmenes enormes vs binariosMuy verboso, parsing más lento que JSON, complejo para casos simples

Otros formatos: YAML (configuraciones, legible, comentarios, sensible a la indentación) · NDJSON (un JSON por línea, streaming) · Avro / Parquet / Protocol Buffers (binarios, eficientes, esquemas tipados; Big Data y microservicios) · INI / .properties (config. sencilla, típico de Java: Properties.load).

Parsers (analizadores sintácticos) y binding

🔍 Parsing

Un parser transforma datos en bruto (texto o binario) en una estructura interna que el programa entiende, según una gramática formal. Primer paso. Resultado: estructura genérica (tokens, árbol DOM, mapas clave/valor, registros CSV).

🔗 Binding (vinculación)

Automatiza la conversión entre documentos y objetos Java del modelo de dominio (POJOs). Va después del parsing. Resultado: objeto Java con atributos y tipos concretos (String, int, LocalDate…).

EXAMENPuede haber parsing sin binding, pero NO binding sin parsing.
CSV: id,nom,email / 1,Ada,ada@ex.com→ parsing + binding →List<Client> [new Client("1","Ada","ada@ex.com"), …]

Los parsers son críticos: puente entre representaciones, abstracción de complejidad, validación y seguridad (comprueban coherencia), eficiencia con grandes volúmenes.

Data binding (mapeo automático)

El desarrollador define clases que describen la estructura y, con anotaciones, esquemas o convenciones de nombres, el parser hace la serialización (objeto → documento) y la deserialización (documento → objeto) de forma transparente.

  • Productividad y código limpio, objetos tipados, validación integrada
  • Pérdida de control fino del flujo, coste inicial de configuración con datos irregulares, más coste computacional (overhead)

Tipos de parsers según el modelo de procesamiento

Árbol (DOM)Eventos PUSHEventos PULLCSV (row-based)
IdeaCarga el documento entero en memoria como árbol navegable y modificable (bidireccional)El parser controla el flujo, emite eventos e invoca callbacks; la app es receptora pasivaEl cliente controla (cursor-driven): pide el siguiente token con next()/nextToken()Línea a línea, registros planos
VentajasNavegación intuitiva, acceso directo a cualquier parte, fácil modificar/transformarMuy eficiente en memoria, arranque rápido, streaming nativo (tiempo real)Más legible que push, control total (avanzar/parar), eficiente en memoria, permite saltar seccionesRápido, simple, universal, grandes volúmenes
InconvenientesMucha memoria (5-10× el tamaño del fichero), lento al inicio, escalabilidad limitadaCódigo complejo (estado manual), solo secuencial, no edita ni da visión completa, difícil depurarEstado manual, complejidad intermedia (más simple que SAX, menos que DOM)Sin jerarquía, sin estándar estricto, sin metadatos/tipos
CuándoDocs pequeños/medianos (hasta 10-50 MB), modificar, consultas aleatorias, transformacionesDocs muy grandes (logs, dumps), solo lectura/extracción, pipelines ETLStreaming con condiciones (saltar/parar), extracción selectiva, docs voluminosos, equilibrio eficiencia/legibilidadImport/export tabular, ETL ligero, Excel/Sheets/ERP
Java XMLDOMSAX, JAXB (binding)StAX, JAXB (binding)Apache Commons CSV, OpenCSV (bean mapping: @CsvBindByName, @CsvBindByPosition)
Java JSONJackson Tree Model, Gson (binding)—Jackson Streaming API (binding)

7 Parser DOM XML árbol

DOM (Document Object Model): representación jerárquica en árbol de un XML cargado en memoria. Estándar del W3C. El parser lee el XML entero, lo valida (si se pide) y construye el árbol; permite navegar, consultar y modificar.

Consideraciones al generar el árbol EXAMEN

  • Aunque haya esquema, DOM no valida salvo que se indique; solo exige que esté bien formado.
  • El primer nodo es el nodo raíz que representa el documento.
  • Los atributos NO generan nodos hijos: forman parte de la información del nodo.
  • Los valores de los elementos son nodos #text hijos del elemento.
  • Los saltos de línea y espacios también son nodos #text.
  • La librería permite ignorar comentarios, espacios en blanco, etc.

Arquitectura

javax.xml.parsers.*

  • DocumentBuilderFactory: crea y configura el parser.
  • DocumentBuilder: analiza el XML y devuelve el Document.
  • ParserConfigurationException.

org.w3c.dom.* (interfaces)

  • Node: base de todo.
  • Document: raíz, acceso a todo.
  • Element: etiqueta · Attr: atributo · Text: contenido · Comment.
  • NodeList: lista ordenada · NamedNodeMap: colección no ordenada con nombre (atributos).

Proceso

1. DocumentBuilderFactory → DocumentBuilder (opciones)→2. builder.parse(...) → árbol→3. normalize() (opcional)→4. Leer / modificar→5. Guardar con Transformer
  • normalize() (doc.getDocumentElement().normalize()): fusiona nodos de texto adyacentes y limpia.
  • Lectura: getElementsByTagName, getAttribute, getTextContent, recorridos con NodeList (getLength(), item(i)).
  • Modificación: setTextContent, setAttribute, appendChild, removeChild, replaceChild, doc.createElement.
  • Serialización: con Transformer (TransformerFactory, DOMSource, StreamResult).
LibrosXML.xml (estructura usada en todos los ejemplos)<Libros>
  <Libro publicado_en="1840">
    <Titulo>...</Titulo>
    <Autor>...</Autor>
  </Libro>
  ...
</Libros>
DOMLibrosExample// 1) Configuración
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); // protección XXE
f.setNamespaceAware(true);
f.setIgnoringElementContentWhitespace(true);
f.setIgnoringComments(true);
DocumentBuilder b = f.newDocumentBuilder();

// 2) Parsear  3) Normalizar
Document doc = b.parse(new File("data/LibrosXML.xml"));
doc.getDocumentElement().normalize();

// 4A) Recorrer con getChildNodes (hay que filtrar los #text → instanceof Element)
NodeList llista = doc.getFirstChild().getChildNodes();
for (int i = 0; i < llista.getLength(); i++) {
    if (llista.item(i) instanceof Element) {
        Node e = llista.item(i);
        String any = e.getAttributes().getNamedItem("publicado_en").getNodeValue();
        NodeList fills = e.getChildNodes();
        for (int j = 0; j < fills.getLength(); j++) {
            if (fills.item(j) instanceof Element) {
                Node fill = fills.item(j);
                if (fill.getNodeName().equals("Titulo")) titol = fill.getTextContent();
                else if (fill.getNodeName().equals("Autor")) autor = fill.getTextContent();
            }
        }
    }
}

// 4B) Recorrer con getElementsByTagName (más cómodo)
NodeList llibres = doc.getElementsByTagName("Libro");
for (int i = 0; i < llibres.getLength(); i++) {
    Element e = (Element) llibres.item(i);
    String any   = e.getAttribute("publicado_en");
    String titol = e.getElementsByTagName("Titulo").item(0).getTextContent();
    String autor = e.getElementsByTagName("Autor").item(0).getTextContent();
}

// 4C) Modificar atributo
if (llibres.getLength() > 0) ((Element) llibres.item(0)).setAttribute("publicado_en", "1842");

// 4D) Añadir nuevo libro
Element arrel = doc.getDocumentElement();
Element nou = doc.createElement("Libro");
nou.setAttribute("publicado_en", "2025");
Element t = doc.createElement("Titulo"); t.setTextContent("Llibre exemple DOM");
Element a = doc.createElement("Autor");  a.setTextContent("Joan Developer");
nou.appendChild(t); nou.appendChild(a);
arrel.appendChild(nou);

// 5) Guardar
Transformer tr = TransformerFactory.newInstance().newTransformer();
tr.setOutputProperty(OutputKeys.INDENT, "yes");
tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
tr.transform(new DOMSource(doc), new StreamResult(new File("data/LibrosXML_modificat.xml")));
Buenas prácticas DOMFiltrado temprano (no recorrer todo el árbol) · normalize() · UTF-8 coherente entre declaración XML y Transformer · setIgnoringElementContentWhitespace(true) (requiere validación activa) · cerrar recursos y excepciones robustas · comprobar longitud de NodeList antes de acceder.

8 Parser DOM JSON (Gson / Jackson Tree) árbol binding

Árbol en memoria de un JSON, similar al DOM XML. No es un estándar W3C, pero es un modelo común (Jackson, Gson, org.json).

Diferencias con el DOM XML EXAMEN

  • Jackson/Gson no validan contra JSON Schema automáticamente; solo comprueban sintaxis (comillas, llaves, comas).
  • El nodo raíz puede ser objeto o array (equivale al Document).
  • Clave y valor son parte del mismo nodo. No hay atributos (Attr).
  • Valores literales (texto, números, booleanos, null) = nodos hoja. No hay nodos #text.
  • Espacios y saltos de línea se ignoran (no son nodos).
  • Comentarios no permitidos por el estándar → error. Jackson puede aceptarlos si se activa la propiedad.

Jackson — com.fasterxml.jackson.databind.*

  • ObjectMapper: clase central; readTree, writeValue, convertValue.
  • JsonNode: cualquier nodo (todos derivan de ella).
  • ObjectNode {}: get, set, put, remove.
  • ArrayNode []: get(index), add().
  • TextNode, IntNode, BooleanNode, NullNode.
  • JsonProcessingException.

Gson (Google + JSON) — com.google.gson.*

  • Más sencilla de aprender, menos rendimiento y funcionalidades que Jackson. En clase empezamos con Gson.
  • Gson: conversiones JSON ↔ Java.
  • GsonBuilder: opciones (pretty print, exclusión de campos, serializeNulls).
  • JsonParser: texto → árbol (JsonElement).
  • Modelo: JsonElement, JsonObject {…}, JsonArray […], JsonPrimitive (literal), JsonNull.

Lectura: get("clau"), getAsString(), getAsInt(), getAsBoolean(), getAsJsonArray(), getAsJsonObject() · Modificación: addProperty("clau", valor), add("clau", JsonElement), remove("clau") · Guardar: gson.toJson(arrel, writer) (con setPrettyPrinting() sale indentado).

Gson SIN binding (árbol)try (Reader reader = new InputStreamReader(new FileInputStream("data/Libros.json"), StandardCharsets.UTF_8)) {
    Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();

    JsonObject arrel = JsonParser.parseReader(reader).getAsJsonObject();   // árbol
    JsonArray llibres = arrel.getAsJsonArray("Libros");
    if (llibres == null) { System.err.println("No hi ha 'Libros'"); return; }

    for (JsonElement e : llibres) {
        JsonObject llibre = e.getAsJsonObject();
        String titol = llibre.get("Titulo").getAsString();
        int any = llibre.get("publicado_en").getAsInt();
    }
    llibres.get(0).getAsJsonObject().addProperty("publicado_en", 1842);    // modificar

    JsonObject nou = new JsonObject();                                      // añadir
    nou.addProperty("Titulo", "DOM i JSON");
    nou.addProperty("Autor", "Maria Dom");
    nou.addProperty("publicado_en", 2025);
    llibres.add(nou);

    try (Writer w = new OutputStreamWriter(new FileOutputStream("data/Libros_modificat.json"), StandardCharsets.UTF_8)) {
        gson.toJson(arrel, w);                                              // guardar
    }
} catch (IOException e) { ...
} catch (IllegalStateException e) { /* formato JSON incorrecto */ }

Gson CON binding (POJO)

POJO (Plain Old Java Object): clase simple con atributos, constructores, getters y setters. Requisitos: nombres de propiedades = claves JSON (si no, anotar con @SerializedName("Clau")), constructor vacío, getters y setters.

Gson CON bindingGson gson = new GsonBuilder().setPrettyPrinting().create();
Biblioteca biblioteca = gson.fromJson(reader, Biblioteca.class);   // JSON → objeto (deserialización)
biblioteca.getLibros().get(0).setPublicadoEn(1842);
biblioteca.getLibros().add(new Libro("Json, Java i Pojo", "Ana Java", 2025));
gson.toJson(biblioteca, writer);                                    // objeto → JSON (serialización)

public class Biblioteca {
    @SerializedName("Libros") private List<Libro> libros;
    // getters/setters
}
public class Libro {
    @SerializedName("Titulo")       private String titulo;
    @SerializedName("Autor")        private String autor;
    @SerializedName("publicado_en") private int publicadoEn;
    public Libro() {}                       // constructor vacío obligatorio
    // constructor completo, getters, setters, toString
}
Buenas prácticas DOM JSONFiltrado temprano · JSON por defecto UTF-8 · JSON no garantiza orden → buscar por clave, no por índice · controlar nulos (isNull() / isJsonNull()) · cerrar recursos · comprobar existencia de nodos y validar campos obligatorios/tipos · pretty print y formatos de fecha coherentes · no permitir comentarios si el origen es externo, evitar inyecciones verificando estructura.

9 Parser SAX XML push

SAX (Simple API for XML): procesamiento secuencial basado en eventos. No carga el documento: lo recorre de inicio a fin y genera eventos que ejecutan métodos callback automáticamente.

Muy eficiente en memoria y rápido. No permite acceso libre a cualquier parte ni modificaciones (unidireccional, sin árbol).

javax.xml.parsers.*

  • SAXParserFactory: crea/configura (validación, namespaces).
  • SAXParser: analiza y genera eventos.
  • ParserConfigurationException

org.xml.sax.*

  • ContentHandler: interfaz con los callbacks.
  • ErrorHandler: errores de sintaxis/validación.
  • EntityResolver: entidades externas (DTD).
  • InputSource: fuente de entrada.

org.xml.sax.helpers.*

  • DefaultHandler: implementa ContentHandler, ErrorHandler, EntityResolver y DTDHandler con métodos vacíos. Se extiende y se sobrescriben solo los que interesan.
CallbackSe ejecuta…
startDocument()al empezar el documento
startElement(uri, localName, qName, Attributes)al abrir una etiqueta → aquí se leen los atributos
characters(char[] ch, int start, int length)al encontrar texto (puede ser espacios/saltos; puede llegar en fragmentos)
endElement(uri, localName, qName)al cerrar una etiqueta → aquí se asigna el texto acumulado
endDocument()al acabar
LibrosHandler extends DefaultHandlerpublic class LibrosHandler extends DefaultHandler {
    private List<Libro> llistaLlibres;
    private Libro llibreActual;
    private StringBuilder contingut;

    @Override public void startDocument() { llistaLlibres = new ArrayList<>(); contingut = new StringBuilder(); }

    @Override public void startElement(String uri, String localName, String qName, Attributes attributes) {
        contingut.setLength(0);
        if ("libro".equalsIgnoreCase(qName)) {
            llibreActual = new Libro();
            String p = attributes.getValue("publicado_en");
            if (p != null) llibreActual.setPublicadoEn(Integer.parseInt(p));
        }
    }

    @Override public void characters(char[] ch, int start, int length) {
        contingut.append(ch, start, length);        // ACUMULAR: puede venir en varios trozos
    }

    @Override public void endElement(String uri, String localName, String qName) {
        String dades = contingut.toString().trim();
        if (llibreActual != null) {
            switch (qName.toLowerCase()) {
                case "titulo": llibreActual.setTitulo(dades); break;
                case "autor":  llibreActual.setAutor(dades);  break;
                case "libro":  llistaLlibres.add(llibreActual); llibreActual = null; break;
            }
        }
        contingut.setLength(0);
    }
    public List<Libro> getLlistaLlibres() { return llistaLlibres; }
}
LibrosSAXReaderSAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(false);
// Seguridad XXE
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

SAXParser parser = factory.newSAXParser();
LibrosHandler handler = new LibrosHandler();
parser.parse("data/LibrosXML.xml", handler);       // el parser llama a los callbacks
List<Libro> llibres = handler.getLlistaLlibres();
Buenas prácticas SAXFiltrado temprano comprobando nombres en startElement() · gestionar estado/contexto con variables o pilas (Stack) · characters() puede fragmentar → StringBuilder · SAX no elimina espacios → comprobar que no sea solo blanco · desactivar entidades externas y DTD (ataques XXE, entity expansion).

10 Parser StAX XML pull

StAX (Streaming API for XML): streaming con modelo pull-parsing; la aplicación cliente mantiene el control síncrono. El parser espera hasta que se llama a next(), avanza al siguiente token y devuelve su tipo.

Gestión determinista del flujo, muy eficiente (solo el nodo actual). Mismas limitaciones que SAX: sin acceso aleatorio ni modificación.

javax.xml.stream.*

ClaseFunción
XMLInputFactoryCrea/configura lectores.
XMLOutputFactoryCrea escritores.
XMLStreamReaderLectura secuencial pull (cursor).
XMLStreamWriterEscritura secuencial de XML.
XMLStreamConstantsConstantes de eventos: START_DOCUMENT, START_ELEMENT, CHARACTERS, END_ELEMENT, END_DOCUMENT…
XMLEventReader / XMLEventWriterAlternativa que encapsula cada token en objetos XMLEvent (más flexible).
XMLStreamExceptionErrores de procesamiento.

Métodos: next() (avanza y devuelve tipo, int) · hasNext() · getEventType() · getLocalName() / getAttributeValue(null,"nom") (en START_ELEMENT) · getText() (en CHARACTERS).

LibrosStaxReaderXMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); // XXE
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);

try (FileInputStream fis = new FileInputStream("data/LibrosXML.xml")) {
    XMLStreamReader reader = factory.createXMLStreamReader(fis);
    Libro llibre = null;
    StringBuilder text = new StringBuilder();

    while (reader.hasNext()) {
        int event = reader.next();                     // EL PROGRAMA pide el siguiente
        switch (event) {
            case XMLStreamConstants.START_ELEMENT:
                text.setLength(0);
                if ("Libro".equals(reader.getLocalName())) {
                    llibre = new Libro();
                    String any = reader.getAttributeValue(null, "publicado_en");
                    if (any != null) llibre.setPublicadoEn(Integer.parseInt(any));
                }
                break;
            case XMLStreamConstants.CHARACTERS:
                text.append(reader.getText());         // acumular
                break;
            case XMLStreamConstants.END_ELEMENT:
                String value = text.toString().trim();
                if (llibre != null) switch (reader.getLocalName()) {
                    case "Titulo": llibre.setTitulo(value); break;
                    case "Autor":  llibre.setAutor(value);  break;
                    case "Libro":  llista.add(llibre); llibre = null; break;
                }
                text.setLength(0);
                break;
        }
    }
    reader.close();   // XMLStreamReader NO es AutoCloseable → close() manual
}
OJOXMLStreamReader no implementa AutoCloseable → no se puede poner en try-with-resources, hay que hacer reader.close().
Buenas prácticas StAXFiltrado temprano con getLocalName() · estado con variables / Stack · CHARACTERS puede venir en varios eventos → StringBuilder hasta END_ELEMENT · no elimina espacios → comprobar vacío · desactivar entidades externas/DTD en la factory (XXE).

11 Jackson Streaming API (JSON) pull binding

Streaming pull-parsing para JSON: el parser espera a nextToken(), avanza y devuelve el tipo de token. Solo el token actual en memoria. Sin acceso aleatorio ni modificación (como StAX). Ideal para documentos grandes o flujos continuos.

com.fasterxml.jackson.core

  • JsonFactory: crea JsonParser y JsonGenerator; opciones (comentarios, apóstrofes como comillas, tolerancia a errores).
  • JsonParser: lectura secuencial pull.
  • JsonToken (enum): START_OBJECT, END_OBJECT, START_ARRAY, END_ARRAY, FIELD_NAME, VALUE_STRING, VALUE_NUMBER_INT, VALUE_TRUE, VALUE_FALSE, VALUE_NULL.
  • JsonGenerator: escritura (writeStartObject(), writeFieldName(), writeString(), writeNumber(), writeEndObject()).
  • JsonProcessingException · JsonFactory.Feature / JsonParser.Feature (configuración).

Métodos: nextToken() · currentName() (nombre del campo) · getValueAsString() / getValueAsInt() · skipChildren() (salta un objeto/array entero sin leerlo).

LibrosJacksonReader (sin binding)JsonFactory factory = new JsonFactory();
factory.enable(JsonParser.Feature.ALLOW_COMMENTS);          // aceptar comentarios

try (JsonParser parser = factory.createParser(new File("data/Libros.json"))) {   // SÍ es AutoCloseable
    if (parser.nextToken() != JsonToken.START_OBJECT) { System.out.println("S'esperava un objecte"); return; }

    while (parser.nextToken() != JsonToken.END_OBJECT) {
        String fieldName = parser.currentName();
        if ("Libros".equals(fieldName)) {
            parser.nextToken();                                  // START_ARRAY
            while (parser.nextToken() != JsonToken.END_ARRAY) {
                if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
                    Libro llibre = new Libro();
                    while (parser.nextToken() != JsonToken.END_OBJECT) {
                        String nomCamp = parser.currentName();   // estamos en FIELD_NAME
                        parser.nextToken();                      // pasar al VALOR
                        switch (nomCamp) {
                            case "Titulo":       llibre.setTitulo(parser.getValueAsString()); break;
                            case "Autor":        llibre.setAutor(parser.getValueAsString());  break;
                            case "publicado_en": llibre.setPublicadoEn(parser.getValueAsInt()); break;
                            default:             parser.skipChildren();
                        }
                    }
                    llista.add(llibre);
                } else parser.skipChildren();
            }
        } else { parser.nextToken(); parser.skipChildren(); }   // otro campo → saltarlo
    }
}

Jackson CON binding (ObjectMapper)

Requisitos: nombres atributos = claves (si no, @JsonProperty("NomCamp")), constructor vacío, getters y setters públicos.

LibrosJacksonReaderBindingObjectMapper mapper = new ObjectMapper();
Biblioteca biblioteca = mapper.readValue(new File("data/Libros.json"), Biblioteca.class); // JSON → objetos
// mapper.writeValue(new File("out.json"), biblioteca);                                    // objetos → JSON

public class Biblioteca {
    @JsonProperty("Libros") private List<Libro> llibres;
    // getters/setters
}
public class Libro {
    @JsonProperty("Titulo")       private String titulo;
    @JsonProperty("Autor")        private String autor;
    @JsonProperty("publicado_en") private int publicadoEn;
    // getters/setters, toString
}
Buenas prácticas JacksonFiltrado temprano (getCurrentName()) · estado con booleanos/estructuras de control · comprobar el tipo de token antes de leer y usar el método adecuado · StringBuilder para textos grandes · JSON no tiene espacios entre valores como XML, pero sí nulos/vacíos → comprobar.

12 API JAXB (XML) binding

JAXB (Java Architecture for XML Binding) no es un parser: es una especificación oficial de Java para mapear XML ↔ objetos Java (bidireccional). Solo define interfaces, anotaciones y comportamiento; no incluye implementación.

  • Necesita una implementación real, que internamente usa un parser de bajo nivel.
  • Implementación oficial: GlassFish JAXB RI (Reference Implementation) → por defecto usa SAX (se puede forzar StAX).
  • Desde Java 11 JAXB no está en el JDK → añadir dependencias: la API (especificación) + la implementación. Paquete: jakarta.xml.bind.

Arquitectura en 3 capas

1. Aplicación (modelo Java)

POJOs anotados con JAXB que definen el mapping. La gestiona el desarrollador.

2. Binding

  • JAXBContext: punto de entrada, contexto para las clases anotadas.
  • Marshaller: Java → XML (indentación, codificación, namespaces).
  • Unmarshaller: XML → Java (valida si se configura).

3. Procesamiento XML

Interna: un parser XML (SAX o StAX) que lee/escribe el XML real.
Truco memoriaMarshal = "empaquetar" el objeto en XML (escribir) · Unmarshal = "desempaquetar" XML en objeto (leer).

Anotaciones EXAMEN

AnotaciónSignificado
@XmlRootElement(name="X")Elemento raíz. Si el nombre coincide, no hace falta name.
@XmlAccessorType(XmlAccessType.PROPERTY)Se anotan getters/setters públicos. Por defecto (o sin anotación).
@XmlAccessorType(XmlAccessType.FIELD)Se anotan los campos privados. Recomendada, hay que ponerla explícitamente.
@XmlType(propOrder={"a","b"})Orden de los elementos (importante para XSD e interoperabilidad; si no, puede variar entre versiones de Java). Aconsejado.
@XmlElement(name="X")Elemento XML.
@XmlAttribute(name="x")Atributo dentro de la etiqueta del elemento de la clase.
@XmlElementWrapper(name="Cont")Envuelve una colección en una etiqueta contenedora (la colección lleva @XmlElement(name="Individual")).
@XmlTransientExcluye un campo del binding.
@XmlSeeAlso · @XmlEnum/@XmlEnumValue · @XmlValueHerencia · enumeraciones · valores.
<!-- @XmlElementWrapper(name="Cont") + @XmlElement(name="Ind") sobre una List -->
<Cont><Ind>...</Ind><Ind>...</Ind></Cont>
Requisitos de las clasesConstructor vacío público: SIEMPRE obligatorio. Getters/setters públicos: obligatorios con PROPERTY (o sin anotación); con FIELD no obligatorios pero recomendables.
JAXB completo@XmlRootElement(name = "Libros")
@XmlAccessorType(XmlAccessType.FIELD)
public class Biblioteca {
    @XmlElement(name = "Libro")
    private List<Libro> llibres;
    public Biblioteca() {}                     // obligatorio
    // getters/setters
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = { "titulo", "autor" })   // orden de los ELEMENTOS (el atributo no va)
public class Libro {
    @XmlElement(name = "Titulo")         private String titulo;
    @XmlElement(name = "Autor")          private String autor;
    @XmlAttribute(name = "publicado_en") private int publicadoEn;
    public Libro() {}
    // constructor completo, getters/setters, toString
}

// Lectura y escritura
JAXBContext context = JAXBContext.newInstance(Biblioteca.class);

Unmarshaller um = context.createUnmarshaller();
Biblioteca biblio = (Biblioteca) um.unmarshal(new File("data/LibrosXML.xml"));     // XML → Java

biblio.getLlibres().add(new Libro("Cien años de soledad", "Gabriel García Márquez", 1967));

Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);                             // indentado
m.marshal(biblio, new File("data/LibrosXML_Escriptura.xml"));                      // Java → XML

P1 Práctica media-manager · estructura

Proyecto Maven, Java 21, paquete cat.institutmarianao.mediamanager. Aquí se aplica toda la teoría anterior: XML (DOM, SAX, StAX, JAXB), JSON (Gson, Jackson), exportaciones y fichero binario.

Estructura del proyectomodel/                      ← lo da la profe, NO se toca
  MediaContent (abstracta)  → code, title, director, genre, releaseDate (LocalDate), platform
  ├─ Movie   (tipo 'P')     → runtime (int, minutos)
  └─ Series  (tipo 'S')     → List<Season>
        Season              → number, List<Episode>
          Episode           → number, title, runtime
  Catalog                   → LinkedHashMap; addContent() asigna códigos 1, 2, 3…

persistence/                ← lo implementa el alumno (los TODO)
  xml/    DomMovieImporter, SaxMovieImporter (+ FilmSaxHandler), StaxSeriesImporter,
          JaxbCatalogImporter (+ xml/jaxb/CatalogXml), DomCatalogExporter
  json/   GsonCatalogImporter (+ json/gson/GsonContents),
          JacksonCatalogImporter (+ json/jackson/JacksonContents),
          ContentFactory (conversión común), JacksonCatalogExporter
  text/   TextCatalogExporter
  binary/ BinaryCatalogDao (RandomAccessFile), BinaryCatalogRecord

Main         ← lanzador: argumentos all, 1…6, 1-dom, 1-sax, 1-stax, 1-jaxb, 2-gson, 2-jackson
data/input/  ← entrada · data/output/ ← ficheros generados
data/input→importador→List<Movie|Series> (código 0)→Main→Catalog.addContent() (asigna código)→exportadores→data/output
TareaQué se haceTecnologíaFichero
1-domImportar películasDOMmovies_dom.xml
1-saxImportar películasSAXfilms_sax.xml
1-staxImportar seriesStAXseries_stax.xml
1-jaxbImportar catálogo (pelis + series)JAXBcatalog_jaxb.xml
2-gson / 2-jacksonImportar catálogo JSONGson / Jacksoncontents_gson.json / contents_jackson.json
3Exportar a textoBufferedWriterlist.txt
4Exportar catálogo unificadoDOM + Transformerunified_catalog.xml
5Exportar catálogo unificadoJackson Data Bindingunified_catalog.json
6Fichero binario de acceso aleatorioRandomAccessFileregistro de 102 bytes

P2 Reglas comunes de los importadores (Javadoc)

ReglaCómo se cumple
input null → NullPointerExceptionObjects.requireNonNull(input, "input"); ANTES del try
E/S tradicional de JavaFileInputStream / InputStreamReader
Cerrar recursostry-with-resources: try (InputStream in = new FileInputStream(input)) { … }
Respetar UTF-8Pasar bytes al parser XML (lee la cabecera encoding) o InputStreamReader(…, StandardCharsets.UTF_8)
No imprimir por consolaNada de System.out / System.err; solo return
Errores → IOException conservando la causacatch (Exception e) { throw new IOException("Error importando " + input + ": " + e.getMessage(), e); }
Datos inválidos: avisar, no descartarFalta un campo o tipo desconocido → throw new IOException(...)
Lista nunca null, en orden del documentonew ArrayList<>() y add en orden
No asignar códigos ni usar CatalogObjetos con código 0; los ids del fichero (sourceId, ref, externalId…) se ignoran
No modificar el modeloSi el fichero no encaja → clases intermedias de binding en otro paquete
Hacer los TODOSustituir solo throw new ExerciseNotImplementedException(...); no tocar firmas, Javadoc, Main ni el modelo
Plantilla general de un importadorpublic List<Movie> importMovies(File input) throws IOException {
    Objects.requireNonNull(input, "input");          // fuera del try
    List<Movie> movies = new ArrayList<>();
    try (InputStream in = new FileInputStream(input)) {
        // parser + recorrido + new Movie(...)
    } catch (Exception e) {
        throw new IOException("Error importando " + input + ": " + e.getMessage(), e);
    }
    return movies;
}

Conversiones: LocalDate.parse("2014-11-07") (formato yyyy-MM-dd) · Integer.parseInt("169").

P3 Práctica · importadores XML

Tarea 1-dom · DomMovieImporter (movies_dom.xml)

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(in);                 // o builder.parse(file)
doc.getDocumentElement().normalize();

NodeList movies = doc.getElementsByTagName("movie");
for (int i = 0; i < movies.getLength(); i++) {    // NodeList: for con índice, no for-each
    Element movie = (Element) movies.item(i);     // item() devuelve Node → cast
    String title = movie.getElementsByTagName("name").item(0).getTextContent().trim();
    String id    = movie.getAttribute("id");      // atributo (se ignora como código)
}
  • getElementsByTagName("x") busca en todos los descendientes; getChildNodes() solo hijos directos incluidos los #text → filtrar con getNodeType() == Node.ELEMENT_NODE.
  • getTextContent() · getAttribute("x") · getTagName().
  • Excepciones: ParserConfigurationException, SAXException (XML mal formado, DOM la usa aunque se llame SAX), IOException.
⚠ Trampa de la prácticaCada <movie> tiene dos <name> (título y el de <director>). item(0) da el título; el director se busca dentro del elemento <director>.

Tarea 1-sax · SaxMovieImporter + FilmSaxHandler (films_sax.xml)

SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
FilmSaxHandler handler = new FilmSaxHandler();
parser.parse(in, handler);
return handler.getMovies();

public class FilmSaxHandler extends DefaultHandler {
    private final List<Movie> movies = new ArrayList<>();
    private final StringBuilder texto = new StringBuilder();
    private String seccion;                       // contexto: creators / classification / availability

    @Override public void startElement(String uri, String localName, String qName, Attributes atts) {
        texto.setLength(0);                       // vaciar buffer
        if (qName.equals("film")) { title = atts.getValue("name"); }   // <film name="Coco"> → atributo
        // guardar la sección padre
    }
    @Override public void characters(char[] ch, int start, int length) {
        texto.append(ch, start, length);          // puede llegar a trozos
    }
    @Override public void endElement(String uri, String localName, String qName) {
        // usar texto.toString().trim(); en </film> → new Movie(...) y add
    }
}

SAX no dice quién es el padre → guardas el contexto tú (String seccion o un boolean). El handler puede ser private static class interna o clase pública aparte.

Tarea 1-stax · StaxSeriesImporter (series_stax.xml)

Jerarquía: <show> → <details> → <season> → <episode>.

XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(in);
while (reader.hasNext()) {
    int evento = reader.next();
    if (evento == XMLStreamConstants.START_ELEMENT) {
        switch (reader.getLocalName()) {
            case "season" -> season = new Season(Integer.parseInt(reader.getAttributeValue(null, "number")));
            case "title"  -> title = reader.getElementText().trim();
        }
    } else if (evento == XMLStreamConstants.END_ELEMENT) {
        switch (reader.getLocalName()) {
            case "season" -> series.addSeason(season);
            case "show"   -> lista.add(series);
        }
    }
}
reader.close();                                   // no es AutoCloseable (el FileInputStream sí va en el try)
  • getElementText() lee el texto y mueve el cursor hasta el cierre → leer los atributos antes.
  • getAttributeValue(null, "x"): null = sin namespace.
  • La Series se crea al cerrar </details> (el constructor necesita el director).
  • <title> aparece en serie y en episodio → flag dentroEpisodio.
  • season.addEpisode(...) y series.addSeason(...).
  • Escribir con StAX: XMLOutputFactory → XMLStreamWriter (writeStartElement, writeAttribute, writeCharacters, writeEndElement).

Tarea 1-jaxb · JaxbCatalogImporter + CatalogXml (catalog_jaxb.xml)

JAXBContext context = JAXBContext.newInstance(CatalogXml.SupplierCatalog.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
CatalogXml.SupplierCatalog catalog = (CatalogXml.SupplierCatalog) unmarshaller.unmarshal(in);
// después: según category (movie / show) → new Movie(...) o new Series(...) + Season + Episode
  • Regla: cada etiqueta con hijos = una clase; cada hijo simple = un campo. Etiquetas sin campo se ignoran (p. ej. sourceCode).
  • Constructor sin argumentos; clases internas public static class.
  • Fechas como String → LocalDate.parse (JAXB no convierte LocalDate solo).
  • Java 21 / JAXB 4 → jakarta.xml.bind.* (no javax). Dependencias: jakarta.xml.bind-api + jaxb-runtime.
  • Clases intermedias en persistence.xml.jaxb (separado del modelo). Excepción: JAXBException.
  • @XmlValue = texto de la propia etiqueta · @XmlElementWrapper(name="entries") para <entries><entry/>…</entries>.
DOMSAXStAXJAXB
Lo que escribesRecorrer nodosUn DefaultHandlerBucle while (hasNext())Clases anotadas
Ficheromovies_dom.xmlfilms_sax.xmlseries_stax.xmlcatalog_jaxb.xml

P4 Práctica · importadores JSON (Gson vs Jackson)

GsonJackson Data Binding
Objeto principalGson (new Gson() / new GsonBuilder().create())ObjectMapper
Leergson.fromJson(reader, Clase.class)mapper.readValue(in, Clase.class)
Escribirgson.toJson(objeto, writer)mapper.writeValue(file, objeto)
EntradaUn Reader → InputStreamReader(…, UTF_8)InputStream, File o Reader (detecta codificación)
Campos desconocidosLos ignora por defectoError por defecto → FAIL_ON_UNKNOWN_PROPERTIES=false o @JsonIgnoreProperties(ignoreUnknown = true)
Renombrar@SerializedName@JsonProperty
Con formatosetPrettyPrinting()enable(SerializationFeature.INDENT_OUTPUT) / writerWithDefaultPrettyPrinter()
Listas genéricasnew TypeToken<List<X>>(){}.getType()new TypeReference<List<X>>(){}
ExcepciónJsonSyntaxExceptionJsonProcessingException / JsonParseException (son IOException)

JSON ↔ Java: { } → clase (POJO) · [ ] → List<…> · "texto" → String · número → int/long/double · true/false → boolean · null → null.

GsonGson gson = new GsonBuilder().setPrettyPrinting().create();
try (Reader reader = new InputStreamReader(new FileInputStream(input), StandardCharsets.UTF_8)) {
    GsonContents.Root root = gson.fromJson(reader, GsonContents.Root.class);   // JSON → objeto
}
JacksonObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try (InputStream in = new FileInputStream(input)) {
    JacksonContents.Root root = mapper.readValue(in, JacksonContents.Root.class);
}

Otras anotaciones Jackson: @JsonPropertyOrder({...}) (orden), @JsonIgnore (excluir), @JsonInclude(JsonInclude.Include.NON_NULL) (no escribir nulos).

Clases intermedias

contents_gson.json → persistence.json.gson

items[] → kind (film/show), name, credits.director, metadata.{genre, releaseDate, availableOn}, content.minutes o content.seasons[].{index, episodes[].{index, name, runtime}}

contents_jackson.json → persistence.json.jackson

records[] → mediaType (MOVIE/SERIES), title, director, genre, releaseDate, platform, movieDetails.durationMinutes o seriesDetails.seasons[].{seasonNumber, chapters[].{chapterNumber, title, durationMinutes}}
ContentFactory: evitar duplicar códigoClase de utilidad (final, constructor private, métodos static): createMovie(...), createSeries(...), createSeason(series, n), addEpisode(season, ...). Cada importador solo lee su formato y llama a la factory.

P5 Práctica · exportaciones (tareas 3, 4, 5)

Reglas comunesReciben objetos ya con código (código 0 → IllegalArgumentException) · UTF-8 con E/S tradicional · sustituyen el fichero (no append) · cierran recursos · no imprimen · la carpeta de salida la crea quien llama.
OrdenaciónComparator<MediaContent> orden = Comparator
        .comparing(MediaContent::getTitle, String.CASE_INSENSITIVE_ORDER)
        .thenComparingInt(MediaContent::getCode);
TareaClaseSalidaDetalles
3TextCatalogExporterlist.txtUna línea por contenido: código;título;director. Todo junto, ordenado por título (sin mayúsculas) y código. BufferedWriter + OutputStreamWriter(…, UTF_8)
4DomCatalogExporterunified_catalog.xmlDOM + Transformer. catalog > movies / series (aunque estén vacíos); pelis y series ordenadas por separado; temporadas y episodios en orden numérico; fechas yyyy-MM-dd; minutos sin unidad
5JacksonCatalogExporterunified_catalog.jsonObjectMapper + clases de binding de salida; arrays movies y series (aunque vacíos), sin propiedades extra. Prohibido concatenar texto JSON, JsonGenerator o Gson
Tarea 4 · escribir con DOMDocument doc = builder.newDocument();
Element catalog = doc.createElement("catalog");
doc.appendChild(catalog);
Element movies = doc.createElement("movies");
catalog.appendChild(movies);
Element movie = doc.createElement("movie");
movie.setAttribute("code", String.valueOf(m.getCode()));
Element title = doc.createElement("title");
title.setTextContent(m.getTitle());
movie.appendChild(title);
movies.appendChild(movie);

Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
try (Writer writer = new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8)) {
    transformer.transform(new DOMSource(doc), new StreamResult(writer));
}
Tarea 5 · escribir con JacksonObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try (Writer writer = new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8)) {
    mapper.writeValue(writer, objetoDeSalida);   // clases de binding de salida
}

Nota: en los ejemplos de escritura DOM los nombres de etiquetas (movie, title, atributo code) son ilustrativos; usa los que pida el enunciado.

P6 Práctica · fichero binario (tarea 6)

BinaryCatalogDao con RandomAccessFile (modos "r" / "rw", seek). La búsqueda devuelve un BinaryCatalogRecord.

CampoTipoBytesMétodo
códigoint4writeInt / readInt
título40 char80writeChars (rellenado con espacios hasta 40) / 40 × readChar
tipochar (P/S)2writeChar / readChar
fechalong (epoch day)8writeLong(date.toEpochDay()) / LocalDate.ofEpochDay(readLong())
temporadasint4writeInt (0 en películas)
episodios totalesint4writeInt (0 en películas)
TOTAL102
FórmulasPosición: (codigo - 1) * 102 → raf.seek(posicion) · Nº registros: raf.length() / 102 · Existe si codigo <= raf.length() / 102
NO CONFUNDIREl ejemplo de clase (Alumnos) tiene registro de 106 bytes (int + 15 chars + 30 chars + int + double). El de la práctica es de 102 bytes.
  • Códigos únicos y seguidos desde 1; el registro va según su código, no según el orden de la colección.
  • Título > 40 caracteres → se rechaza con excepción (nunca se recorta).
  • Al leer se quitan los espacios de relleno: stripTrailing().
  • Al crear se reemplaza entero: raf.setLength(0).
  • Fichero inexistente o corrupto → IOException; código que no está → Optional.empty().
Esqueleto orientativoprivate static final int TITLE_CHARS = 40;
private static final int RECORD_SIZE = 4 + TITLE_CHARS * 2 + 2 + 8 + 4 + 4; // 102

// escribir
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
    raf.setLength(0);                                       // reemplazar
    for (MediaContent c : contents) {
        if (c.getTitle().length() > TITLE_CHARS) throw new IllegalArgumentException("Título demasiado largo");
        raf.seek((long) (c.getCode() - 1) * RECORD_SIZE);   // según código
        raf.writeInt(c.getCode());
        raf.writeChars(String.format("%-40s", c.getTitle())); // rellenar a 40
        raf.writeChar(c instanceof Movie ? 'P' : 'S');
        raf.writeLong(c.getReleaseDate().toEpochDay());
        raf.writeInt(temporadas);                           // 0 en películas
        raf.writeInt(episodiosTotales);
    }
}

// buscar
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
    if (codigo < 1 || codigo > raf.length() / RECORD_SIZE) return Optional.empty();
    raf.seek((long) (codigo - 1) * RECORD_SIZE);
    int code = raf.readInt();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < TITLE_CHARS; i++) sb.append(raf.readChar());
    String title = sb.toString().stripTrailing();
    char tipo = raf.readChar();
    LocalDate fecha = LocalDate.ofEpochDay(raf.readLong());
    int temporadas = raf.readInt(), episodios = raf.readInt();
    return Optional.of(new BinaryCatalogRecord(/* ... */));
}

El esqueleto es orientativo (nombres de getters y constructor de BinaryCatalogRecord según tu código; la excepción concreta para títulos largos, según el Javadoc).

P7 Práctica · conceptos y errores típicos

Conceptos

  • Deserializar / unmarshal / parsear: documento → objetos. Serializar / marshal: objetos → documento.
  • Clase intermedia (DTO de binding): copia la forma del fichero externo para no tocar el modelo; luego se convierte.
  • XML bien formado: una sola raíz, etiquetas cerradas y bien anidadas, atributos entre comillas, distingue mayúsculas. Válido: además cumple DTD o XSD.
  • Elemento vs atributo: <film name="Coco"> → atributo; <name>Coco</name> → elemento con texto.
  • Puente bytes→texto: InputStreamReader / OutputStreamWriter con StandardCharsets.UTF_8.
  • Encadenar excepciones: new IOException(mensaje, causa) conserva el error original (getCause()).

Errores típicos

  1. getElementsByTagName("name") devuelve también el <name> del director.
  2. Olvidar texto.setLength(0) en SAX → los textos se acumulan.
  3. Leer atributos después de getElementText() en StAX.
  4. Crear la Series antes de tener el director.
  5. javax.xml.bind en vez de jakarta.xml.bind.
  6. Clases internas JAXB sin static o sin constructor vacío.
  7. Jackson sin FAIL_ON_UNKNOWN_PROPERTIES = false con campos extra (externalId, provider).
  8. Gson sin UTF-8 en el Reader → acentos mal.
  9. Usar los ids del fichero como códigos (los pone Catalog).
  10. Dejar throw new ExerciseNotImplementedException o meterlo en el catch (Main mostraría NOT_IMPLEMENTED).
  11. System.out dentro de importadores/exportadores.
  12. requireNonNull dentro del try → el catch lo convierte en IOException.
  13. Nombres de variables que no coinciden (movieEl vs movieE1, episodNumber vs episodeNumber).
  14. Binario: tamaño de registro mal (char = 2 bytes) o posición mal ((codigo - 1) * 102).

13 Tablas resumen para el examen

Parsers: quién es quién

APIFormatoModelo¿Quién controla?Memoria¿Modifica?Clases clave
DOMXMLÁrbol—Alta (todo)✔ (Transformer)DocumentBuilderFactory, DocumentBuilder, Document, Element, NodeList
Gson / Jackson TreeJSONÁrbol + binding—Alta✔ (toJson)Gson, GsonBuilder, JsonParser, JsonObject, JsonArray / ObjectMapper, JsonNode
SAXXMLPushEl parser (callbacks)Baja✘SAXParserFactory, SAXParser, DefaultHandler
StAXXMLPullEl programa (next())Baja✘ (escribe con XMLStreamWriter)XMLInputFactory, XMLStreamReader, XMLStreamConstants
Jackson StreamingJSONPull + bindingEl programa (nextToken())Baja✘ (escribe con JsonGenerator)JsonFactory, JsonParser, JsonToken, ObjectMapper
JAXBXMLBinding (usa SAX/StAX)——✔ (marshal)JAXBContext, Marshaller, Unmarshaller

Anotaciones de binding

LibreríaRenombrar campo ↔ clave/etiquetaRequisitos
Gson@SerializedName("X")Constructor vacío + getters/setters
Jackson@JsonProperty("X")Constructor vacío + getters/setters públicos
JAXB@XmlElement, @XmlAttribute, @XmlRootElement…Constructor vacío público siempre; getters/setters si PROPERTY
OpenCSV@CsvBindByName, @CsvBindByPosition—

Lectura / escritura por API

APILeerEscribir
BytesFileInputStream.read() → -1FileOutputStream.write(int)
TextoBufferedReader.readLine() → nullBufferedWriter.write() + newLine()
RandomAccessFileseek() + readInt/readChar/readDoubleseek() + writeInt/writeChar/writeDouble
SerializaciónObjectInputStream.readObject() + castObjectOutputStream.writeObject()
DOM XMLbuilder.parse(file)Transformer.transform(DOMSource, StreamResult)
GsonJsonParser.parseReader() / gson.fromJson(r, X.class)gson.toJson(obj, writer)
Jacksonmapper.readValue(f, X.class) / readTreemapper.writeValue(f, obj)
JAXBunmarshaller.unmarshal(file)marshaller.marshal(obj, file)

14 Trampas típicas de examen

  1. File no lee ni escribe contenido; es solo la ruta. Crear un File no crea el fichero.
  2. delete() sobre un directorio solo funciona si está vacío. mkdirs() crea padres, mkdir() no.
  3. getCanonicalPath() lanza IOException; getAbsolutePath() no limpia ...
  4. El GC no cierra ficheros. try-with-resources (Java 7) cierra en orden inverso.
  5. Excepciones de ficheros = checked. FileNotFoundException es subclase de IOException → en multi-catch van primero las más específicas.
  6. "r" exige que exista; "rw" lo crea si no existe.
  7. char ocupa 2 bytes en RandomAccessFile (writeChar). Un String de 15 chars = 30 bytes.
  8. Leer en el mismo orden que se escribió. Para añadir: seek(length()).
  9. transient y static no se serializan. Sin serialVersionUID coincidente → InvalidClassException. readObject también lanza ClassNotFoundException.
  10. DOM XML: atributos ≠ nodos hijos; los textos sí son nodos #text (y los espacios también).
  11. DOM JSON: no hay atributos ni nodos #text; espacios ignorados; comentarios no permitidos (Jackson puede activarlos con ALLOW_COMMENTS).
  12. SAX = push (el parser manda, callbacks, DefaultHandler). StAX/Jackson Streaming = pull (el programa manda con next/nextToken).
  13. characters() puede llegar troceado → StringBuilder.
  14. JAXB no es un parser sino una especificación; desde Java 11 no está en el JDK; RI usa SAX por defecto.
  15. JAXB: FIELD recomendado (explícito), PROPERTY por defecto. Marshaller = Java→XML.
  16. DOM consume 5-10 veces el tamaño del fichero; recomendado hasta 10-50 MB.
  17. XML se valida con XSD/DTD; JSON (opcional) con JSON Schema. JSON es más compacto; XML más verboso.
  18. Binding sin parsing: imposible.

🎮 Quiz tipo Kahoot