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.
| RA | Contenido |
|---|---|
| RA1 | Ficheros: directorios y rutas, ficheros de texto y binarios, secuenciales y de acceso directo, serialización y tratamiento de excepciones. |
| RA2 | BD relacionales con conectores (JDBC): crear/modificar esquemas, sentencias SQL, transacciones, procedimientos almacenados. |
| RA3 | ORM (Hibernate / JPA): mapeo objetos↔tablas, clases persistentes, anotaciones o ficheros de configuración, CRUD y consultas avanzadas con transacciones. |
| RA4 | BD objeto-relacionales y orientadas a objetos. |
| RA5 | BD documentales nativas (NoSQL): info flexible y semiestructurada, colecciones, conexiones, documentos y consultas. |
| RA6 | Componentes de acceso a datos (transversal): componentes reutilizables que encapsulan el acceso a datos, aplicando patrones de diseño. |
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:
GETrecuperar ·POSTcrear/enviar nuevo ·PUT/PATCHactualizar ·DELETEeliminar. - 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).
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).
La clase java.io.File
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
| Path | Qué es | Método |
|---|---|---|
| Relativo | Respecto al directorio de trabajo actual (working directory). | getPath() (tal cual se pasó) |
| Absoluto | Ruta completa, no depende de dónde se ejecuta. | getAbsolutePath() |
| Canónico | Versió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étodo | Devuelve / 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ón | Cuándo |
|---|---|
FileNotFoundException | El fichero no existe o no hay permisos. |
IOException | Error genérico de E/S (disco lleno, error de lectura…). Es la "madre" de las anteriores. |
EOFException | Fin de fichero alcanzado de forma inesperada. |
SecurityException | Permisos denegados. |
NoSuchFileException, FileAlreadyExistsException | NIO.2, más específicas (copiar/mover). |
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
tryy se cierran automáticamente (haya excepción o no) en orden inverso al de declaración. Evita resource leaks. El recurso debe implementarAutoCloseable.
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 bytes | Flujos de caracteres | Flujos con buffer | |
|---|---|---|---|
| Datos | Binarios, unidades de 8 bits (0-255), sin interpretar | Texto, 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 |
| Uso | Imágenes, audio, ZIP | .txt, .csv, .xml… | Siempre que se pueda (eficiencia) |
| Lectura | InputStream (abstracta) → FileInputStream | Reader (base) → FileReader | BufferedInputStream · BufferedReader |
| Escritura | OutputStream (abstracta) → FileOutputStream | Writer (base) → FileWriter | BufferedOutputStream · BufferedWriter |
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();
}
read() 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).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/writelo 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
| boolean | byte | char | short | int | long | float | double |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 2 (UTF-16) | 2 | 4 | 8 | 4 (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)
Posición del registro con id (empezando en 1):
(id - 1) * MIDA_REGISTRE · Nº registros: raf.length() / MIDA_REGISTRE · Nuevo id: length()/MIDA_REGISTRE + 1GestorAlumnes (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.
- 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 conObjectOutputStream(writeObject), lectura conObjectInputStream(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
staticno se serializan (son de la clase, no de la instancia).
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.
| CSV | JSON | XML | |
|---|---|---|---|
| Qué es | Texto plano; cada línea un registro, campos separados por , (o ; \t) | Texto ligero con objetos (clave/valor) y arrays | Basado en etiquetas con jerarquía |
| Uso | Datos tabulares: BD ↔ Excel, ETL, informes | APIs REST, configuración, datos jerárquicos, web y móvil | Sistemas empresariales, servicios web antiguos (SOAP), configuración, legacy |
| Ventajas | Muy sencillo y legible, compatible con ofimática, ficheros pequeños y rápidos | Legible, estructuras complejas, librerías (Jackson, Gson), más compacto que XML | Jerarquía clara, validación con XSD o DTD, namespaces, comentarios, atributos, binding con JAXB |
| Inconvenientes | Sin estándar único (delimitadores, comillas, codificación), solo tablas planas, sin validación ni tipos | Sin esquema obligatorio (opcional JSON Schema), no permite comentarios, ineficiente en volúmenes enormes vs binarios | Muy 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…).
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 PUSH | Eventos PULL | CSV (row-based) | |
|---|---|---|---|---|
| Idea | Carga 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 pasiva | El cliente controla (cursor-driven): pide el siguiente token con next()/nextToken() | Línea a línea, registros planos |
| Ventajas | Navegación intuitiva, acceso directo a cualquier parte, fácil modificar/transformar | Muy eficiente en memoria, arranque rápido, streaming nativo (tiempo real) | Más legible que push, control total (avanzar/parar), eficiente en memoria, permite saltar secciones | Rápido, simple, universal, grandes volúmenes |
| Inconvenientes | Mucha memoria (5-10× el tamaño del fichero), lento al inicio, escalabilidad limitada | Código complejo (estado manual), solo secuencial, no edita ni da visión completa, difícil depurar | Estado manual, complejidad intermedia (más simple que SAX, menos que DOM) | Sin jerarquía, sin estándar estricto, sin metadatos/tipos |
| Cuándo | Docs pequeños/medianos (hasta 10-50 MB), modificar, consultas aleatorias, transformaciones | Docs muy grandes (logs, dumps), solo lectura/extracción, pipelines ETL | Streaming con condiciones (saltar/parar), extracción selectiva, docs voluminosos, equilibrio eficiencia/legibilidad | Import/export tabular, ETL ligero, Excel/Sheets/ERP |
| Java XML | DOM | SAX, JAXB (binding) | StAX, JAXB (binding) | Apache Commons CSV, OpenCSV (bean mapping: @CsvBindByName, @CsvBindByPosition) |
| Java JSON | Jackson 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
#texthijos 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 elDocument.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
- normalize() (
doc.getDocumentElement().normalize()): fusiona nodos de texto adyacentes y limpia. - Lectura:
getElementsByTagName,getAttribute,getTextContent, recorridos conNodeList(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")));
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
}
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.
| Callback | Se 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();
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.*
| Clase | Función |
|---|---|
XMLInputFactory | Crea/configura lectores. |
XMLOutputFactory | Crea escritores. |
XMLStreamReader | Lectura secuencial pull (cursor). |
XMLStreamWriter | Escritura secuencial de XML. |
XMLStreamConstants | Constantes de eventos: START_DOCUMENT, START_ELEMENT, CHARACTERS, END_ELEMENT, END_DOCUMENT… |
XMLEventReader / XMLEventWriter | Alternativa que encapsula cada token en objetos XMLEvent (más flexible). |
XMLStreamException | Errores 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
}
XMLStreamReader no implementa AutoCloseable → no se puede poner en try-with-resources, hay que hacer reader.close().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: creaJsonParseryJsonGenerator; 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
}
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.Anotaciones EXAMEN
| Anotación | Significado |
|---|---|
@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")). |
@XmlTransient | Excluye un campo del binding. |
@XmlSeeAlso · @XmlEnum/@XmlEnumValue · @XmlValue | Herencia · enumeraciones · valores. |
<!-- @XmlElementWrapper(name="Cont") + @XmlElement(name="Ind") sobre una List -->
<Cont><Ind>...</Ind><Ind>...</Ind></Cont>
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
| Tarea | Qué se hace | Tecnología | Fichero |
|---|---|---|---|
| 1-dom | Importar películas | DOM | movies_dom.xml |
| 1-sax | Importar películas | SAX | films_sax.xml |
| 1-stax | Importar series | StAX | series_stax.xml |
| 1-jaxb | Importar catálogo (pelis + series) | JAXB | catalog_jaxb.xml |
| 2-gson / 2-jackson | Importar catálogo JSON | Gson / Jackson | contents_gson.json / contents_jackson.json |
| 3 | Exportar a texto | BufferedWriter | list.txt |
| 4 | Exportar catálogo unificado | DOM + Transformer | unified_catalog.xml |
| 5 | Exportar catálogo unificado | Jackson Data Binding | unified_catalog.json |
| 6 | Fichero binario de acceso aleatorio | RandomAccessFile | registro de 102 bytes |
P2 Reglas comunes de los importadores (Javadoc)
| Regla | Cómo se cumple |
|---|---|
input null → NullPointerException | Objects.requireNonNull(input, "input"); ANTES del try |
| E/S tradicional de Java | FileInputStream / InputStreamReader |
| Cerrar recursos | try-with-resources: try (InputStream in = new FileInputStream(input)) { … } |
| Respetar UTF-8 | Pasar bytes al parser XML (lee la cabecera encoding) o InputStreamReader(…, StandardCharsets.UTF_8) |
| No imprimir por consola | Nada de System.out / System.err; solo return |
Errores → IOException conservando la causa | catch (Exception e) { throw new IOException("Error importando " + input + ": " + e.getMessage(), e); } |
| Datos inválidos: avisar, no descartar | Falta un campo o tipo desconocido → throw new IOException(...) |
| Lista nunca null, en orden del documento | new ArrayList<>() y add en orden |
No asignar códigos ni usar Catalog | Objetos con código 0; los ids del fichero (sourceId, ref, externalId…) se ignoran |
| No modificar el modelo | Si el fichero no encaja → clases intermedias de binding en otro paquete |
| Hacer los TODO | Sustituir 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 congetNodeType() == Node.ELEMENT_NODE.getTextContent()·getAttribute("x")·getTagName().- Excepciones:
ParserConfigurationException,SAXException(XML mal formado, DOM la usa aunque se llame SAX),IOException.
<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
Seriesse crea al cerrar</details>(el constructor necesita el director). <title>aparece en serie y en episodio → flagdentroEpisodio.season.addEpisode(...)yseries.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.*(nojavax). 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>.
| DOM | SAX | StAX | JAXB | |
|---|---|---|---|---|
| Lo que escribes | Recorrer nodos | Un DefaultHandler | Bucle while (hasNext()) | Clases anotadas |
| Fichero | movies_dom.xml | films_sax.xml | series_stax.xml | catalog_jaxb.xml |
P4 Práctica · importadores JSON (Gson vs Jackson)
| Gson | Jackson Data Binding | |
|---|---|---|
| Objeto principal | Gson (new Gson() / new GsonBuilder().create()) | ObjectMapper |
| Leer | gson.fromJson(reader, Clase.class) | mapper.readValue(in, Clase.class) |
| Escribir | gson.toJson(objeto, writer) | mapper.writeValue(file, objeto) |
| Entrada | Un Reader → InputStreamReader(…, UTF_8) | InputStream, File o Reader (detecta codificación) |
| Campos desconocidos | Los ignora por defecto | Error por defecto → FAIL_ON_UNKNOWN_PROPERTIES=false o @JsonIgnoreProperties(ignoreUnknown = true) |
| Renombrar | @SerializedName | @JsonProperty |
| Con formato | setPrettyPrinting() | enable(SerializationFeature.INDENT_OUTPUT) / writerWithDefaultPrettyPrinter() |
| Listas genéricas | new TypeToken<List<X>>(){}.getType() | new TypeReference<List<X>>(){} |
| Excepción | JsonSyntaxException | JsonProcessingException / 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)
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);
| Tarea | Clase | Salida | Detalles |
|---|---|---|---|
| 3 | TextCatalogExporter | list.txt | Una 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) |
| 4 | DomCatalogExporter | unified_catalog.xml | DOM + 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 |
| 5 | JacksonCatalogExporter | unified_catalog.json | ObjectMapper + 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.
| Campo | Tipo | Bytes | Método |
|---|---|---|---|
| código | int | 4 | writeInt / readInt |
| título | 40 char | 80 | writeChars (rellenado con espacios hasta 40) / 40 × readChar |
| tipo | char (P/S) | 2 | writeChar / readChar |
| fecha | long (epoch day) | 8 | writeLong(date.toEpochDay()) / LocalDate.ofEpochDay(readLong()) |
| temporadas | int | 4 | writeInt (0 en películas) |
| episodios totales | int | 4 | writeInt (0 en películas) |
| TOTAL | 102 | ||
(codigo - 1) * 102 → raf.seek(posicion) · Nº registros: raf.length() / 102 · Existe si codigo <= raf.length() / 102- 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/OutputStreamWriterconStandardCharsets.UTF_8. - Encadenar excepciones:
new IOException(mensaje, causa)conserva el error original (getCause()).
Errores típicos
getElementsByTagName("name")devuelve también el<name>del director.- Olvidar
texto.setLength(0)en SAX → los textos se acumulan. - Leer atributos después de
getElementText()en StAX. - Crear la
Seriesantes de tener el director. javax.xml.binden vez dejakarta.xml.bind.- Clases internas JAXB sin
statico sin constructor vacío. - Jackson sin
FAIL_ON_UNKNOWN_PROPERTIES = falsecon campos extra (externalId,provider). - Gson sin UTF-8 en el Reader → acentos mal.
- Usar los ids del fichero como códigos (los pone
Catalog). - Dejar
throw new ExerciseNotImplementedExceptiono meterlo en el catch (Mainmostraría NOT_IMPLEMENTED). System.outdentro de importadores/exportadores.requireNonNulldentro del try → el catch lo convierte en IOException.- Nombres de variables que no coinciden (
movieElvsmovieE1,episodNumbervsepisodeNumber). - 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
| API | Formato | Modelo | ¿Quién controla? | Memoria | ¿Modifica? | Clases clave |
|---|---|---|---|---|---|---|
| DOM | XML | Árbol | — | Alta (todo) | ✔ (Transformer) | DocumentBuilderFactory, DocumentBuilder, Document, Element, NodeList |
| Gson / Jackson Tree | JSON | Árbol + binding | — | Alta | ✔ (toJson) | Gson, GsonBuilder, JsonParser, JsonObject, JsonArray / ObjectMapper, JsonNode |
| SAX | XML | Push | El parser (callbacks) | Baja | ✘ | SAXParserFactory, SAXParser, DefaultHandler |
| StAX | XML | Pull | El programa (next()) | Baja | ✘ (escribe con XMLStreamWriter) | XMLInputFactory, XMLStreamReader, XMLStreamConstants |
| Jackson Streaming | JSON | Pull + binding | El programa (nextToken()) | Baja | ✘ (escribe con JsonGenerator) | JsonFactory, JsonParser, JsonToken, ObjectMapper |
| JAXB | XML | Binding (usa SAX/StAX) | — | — | ✔ (marshal) | JAXBContext, Marshaller, Unmarshaller |
Anotaciones de binding
| Librería | Renombrar campo ↔ clave/etiqueta | Requisitos |
|---|---|---|
| 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
| API | Leer | Escribir |
|---|---|---|
| Bytes | FileInputStream.read() → -1 | FileOutputStream.write(int) |
| Texto | BufferedReader.readLine() → null | BufferedWriter.write() + newLine() |
| RandomAccessFile | seek() + readInt/readChar/readDouble | seek() + writeInt/writeChar/writeDouble |
| Serialización | ObjectInputStream.readObject() + cast | ObjectOutputStream.writeObject() |
| DOM XML | builder.parse(file) | Transformer.transform(DOMSource, StreamResult) |
| Gson | JsonParser.parseReader() / gson.fromJson(r, X.class) | gson.toJson(obj, writer) |
| Jackson | mapper.readValue(f, X.class) / readTree | mapper.writeValue(f, obj) |
| JAXB | unmarshaller.unmarshal(file) | marshaller.marshal(obj, file) |
14 Trampas típicas de examen
- File no lee ni escribe contenido; es solo la ruta. Crear un File no crea el fichero.
delete()sobre un directorio solo funciona si está vacío.mkdirs()crea padres,mkdir()no.getCanonicalPath()lanza IOException;getAbsolutePath()no limpia...- El GC no cierra ficheros. try-with-resources (Java 7) cierra en orden inverso.
- Excepciones de ficheros = checked. FileNotFoundException es subclase de IOException → en multi-catch van primero las más específicas.
"r"exige que exista;"rw"lo crea si no existe.charocupa 2 bytes en RandomAccessFile (writeChar). Un String de 15 chars = 30 bytes.- Leer en el mismo orden que se escribió. Para añadir:
seek(length()). - transient y static no se serializan. Sin serialVersionUID coincidente → InvalidClassException. readObject también lanza ClassNotFoundException.
- DOM XML: atributos ≠ nodos hijos; los textos sí son nodos
#text(y los espacios también). - DOM JSON: no hay atributos ni nodos #text; espacios ignorados; comentarios no permitidos (Jackson puede activarlos con
ALLOW_COMMENTS). - SAX = push (el parser manda, callbacks, DefaultHandler). StAX/Jackson Streaming = pull (el programa manda con next/nextToken).
characters()puede llegar troceado → StringBuilder.- JAXB no es un parser sino una especificación; desde Java 11 no está en el JDK; RI usa SAX por defecto.
- JAXB: FIELD recomendado (explícito), PROPERTY por defecto. Marshaller = Java→XML.
- DOM consume 5-10 veces el tamaño del fichero; recomendado hasta 10-50 MB.
- XML se valida con XSD/DTD; JSON (opcional) con JSON Schema. JSON es más compacto; XML más verboso.
- Binding sin parsing: imposible.