====== Acceso a ficheros ====== ===== Tipos de ficheros ===== Desde el punto de vista de un programador solamente distinguiremos entre dos tipos de ficheros: * **Ficheros de texto** cuando el contenido del fichero contenga exclusivamente caracteres de texto (podemos leerlo con un simple editor de texto) ^ Extensión ^ Tipo de fichero | | .txt | Fichero de texto plano | | .xml | Fichero XML | | .json | Fichero de intercambio de información | | .props | Fichero de propiedades | | .conf | Fichero de configuración | | .sql | Script SQL | | .srt | Fichero de subtítulo | * **Ficheros binarios** cuando no estén compuestos exclusivamente de texto. Pueden contener imágenes, videos, ficheros, . . . aunque también podemos considerar un fichero binario a un fichero de Microsoft Word en el que sólo hayamos escrito algún texto puesto que, al almacenarse el fichero, el procesador de texto incluye alguna información binaria ^ Extensión ^ Tipo de fichero | | .pdf | Fichero PDF | | .jpg | Fichero de imagen | | .doc, .docx | Fichero de Microsoft Word | | .avi | Fichero de video | | .ppt, .pptx | Fichero de PowerPoint | A veces, en ficheros binarios, podremos encontrarnos con las extensiones //.bin// o //.dat// para hacer referencia a ficheros que contienen información binaria en un formato que no está ampliamente difundido. Serán simplemente ficheros que una aplicación determinada es capaz de leer/escribir de una forma específica solo definida para dicha aplicación. ==== Propiedades del Sistema ==== En cualquier caso, para el acceso a ficheros independientemente del tipo de los mismos, conviene conocer el funcionamiento de las [[https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html|System Properties]] de las que dispone Java en su API. Éstas permiten acceder a propiedades de la configuración del sistema y, entre otras, podemos encontrarnos con algunas muy interesantes relacionadas con este tema: * ''"file.separator"'' Obtiene el caracter, según el S.Operativo, para la separación de las rutas (''/'' ó ''\''). También se puede utilizar la constante ''File.separator'' * ''"user.home"'' Obtiene la ruta de la carpeta personal del usuario (que dependerá del S.Operativo en casa caso) * ''"user.dir"'' Obtiene la ruta en la que se encuentra actualmente el usuario * ''"line.separator"'' Obtiene el caracter que separa las líneas de un fichero de texto (difiere entre Windows/Linux) En el caso de que se quiera acceder al valor de alguna de estas propiedades debe hacerse utilizando la llamada al método ''System.getProperty(String)'' System.out.println("La carpeta de mi usuario es " + System.getProperty("user.home")); ===== Ficheros de texto ====== En esta parte vamos a trabajar con 3 tipos de ficheros de texto: * **Ficheros de texto plano** que contendrán texto //libre// y donde podremos escribir sin respetar ningún tipo de formato * **Ficheros de configuración** que contendrán información de configuración para una aplicación. Tienen un formato específico y Java además proporciona una API para trabajar más cómodamente con ellos * **Ficheros XML** que contienen información y acompañada de etiquetas que le dan significado. Tienen unas reglas y formato más o menos definido y Java proporciona una API para trabajar con ellos Existen más tipos de ficheros de texto también muy extendidos como //.json// y //.csv// pero no serán estudiados en esta parte. ==== Ficheros de texto plano ===== Los ficheros de texto son aquellos que únicamente contienen texto, por lo que pueden ser editados directamente con cualquier editor de texto plano (Bloc de Notas, notepad++, . . .). Se podría decir que son aquellos ficheros que podrían ser //leídos// por cualquier persona. Son aquellos que normalmente se almacenan con la extensión //.txt// pero también podríamos incluir los scripts SQL (//.sql//), ficheros de código Java (//.java//), ficheros de configuración (//.ini//, //.props//, //.conf//, . . .), . . . También se incluyen en la categoría de ficheros de texto los que además incluyen información adicional (siempre en forma de texto) que permiten interpretar los datos del fichero de una manera u otra, añadiendo más información al mismo. Estos formatos son HTML, XML, JSON, . . . En cualquier caso, desde Java siempre se podrán leer/escribir de la misma manera, según veremos a continuación. También veremos como pueden leerse/escribirse utilizando librerías aquellos ficheros de texto plano que contienen formato, como hemos comentado anteriormente, haciendo de esa forma mucho más fácil el trabajo. === Escribir ficheros de texto plano === PrintWriter escritor = null; escritor = new PrintWriter("archivo.txt") ; escritor.println("Esto es una linea del fichero"); escritor.close(); === Leer ficheros de texto plano === File fichero = new File("fichero.txt"); Scanner lector = null; lector = new Scanner(fichero); while(lector.hasNextLine()){ System.out.println(lector.nextLine()); } lector.close(); === Otras formas de escribir/leer en ficheros de texto === * Escribir Con FileWriter FileWriter writer = new FileWriter("fichero.txt"); writer.write("Texto a fichero \n"); writer.close(); //Si quiero que no me sobrescriba el fichero destino FileWriter writer = new FileWriter("fichero.txt", true); * Con BufferedWriter / Reader BufferedWriter writer = new BufferedWriter("fichero.txt"); writer.write("texto a escribir\n"); writer.close(); BufferedReader reader = new BufferedReader("fichero.txt"); cadena = reader.readLine(); //Debo leer antes porque igual no hay lineas //Mientras haya lineas para leer (no devuelva null) while( cadena != null ){ System.out.println(cadena); cadena = reader.readline(); } reader.close(); ==== Ficheros de configuración ==== En la API de Java se incluyen librerías para trabajar con los ficheros de configuración. Puesto que todos siguen un mismo patrón, es la librería la que se encarga de acceder al fichero a bajo nivel y el programador sólo tiene que indicar a que propiedad quiere acceder o que propiedad quiere modificar, sin tener que añadir nada de código para leer o escribir el fichero tal y como hemos visto en el punto anterior. Un ejemplo de fichero de configuración de esta librería de java sería el fichero que sigue: # Fichero de configuracion # Thu Nov 14 10:49:39 CET 2013 user=usuario password=micontrasena server=localhost port=3306 === Escribir ficheros de configuración === Así, si queremos generar, desde Java, un fichero de configuración como el anterior: . . . Properties configuracion = new Properties(); configuracion.setProperty("user", miUsuario); configuracion.setProperty("password", miContrasena); configuracion.setProperty("server", elServidor); configuracion.setProperty("port", elPuerto); try { configuracion.store(new FileOutputStream("configuracion.conf"), "Fichero de configuracion"); } catch (FileNotFoundException fnfe ) { fnfe.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } . . . === Leer ficheros de configuración === A la hora de leerlo, en vez de tener que recorrer todo el fichero como suele ocurrir con los ficheros de texto, simplemente tendremos que cargarlo e indicar de qué propiedad queremos obtener su valor (''getProperty(String)''). . . . Properties configuracion = new Properties(); try { configuracion.load(new FileInputStream("configuracion.conf")); usuario = configuracion.getProperty("user"); password = configuracion.getProperty("password"); servidor = configuracion.getProperty("server"); puerto = Integer.valueOf(configuration.getProperty("port")); } catch (FileNotFoundException fnfe ) { fnfe.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } . . . Para ambos casos, escribir y leer este tipo de ficheros, hay que tener en cuenta que, al tratarse de ficheros de texto, toda la información se almacena como si de un ''String'' se tratara. Por tanto, todos aquellos tipos ''LocalDate'', ''boolean'' o incluso cualquier tipo numérico serán almacenados en formato texto. Así, habrá que tener en cuenta las siguientes consideraciones: * Para el caso de las fechas, deberán ser convertidas a texto cando se quieran escribir y nuevamente reconvertidas a ''LocalDate'' cuando se lea el fichero y queramos trabajar con ellas * Para el caso de los tipos ''boolean'', podemos usar el método ''String.valueOf(boolean)'' para pasarlos a ''String'' cuando queramos escribirlos. En caso de que queramos leer el fichero y pasar el valor a tipo ''boolean'' podremos usar el método ''Boolean.parseBoolean(String)'' * Para el caso de los tipos numéricos (''integer'', ''float'', ''double'') es muy sencillo ya que Java los convertirá a ''String'' cuando sea necesario al escribir el fichero. En el caso de que queramos leerlo y convertirlos a su tipo concreto, podremos usar los métodos ''Integer.parseInt(String)'', ''Float.parseFloat(String)'' y ''Double.parseDouble()'', según proceda ==== Ficheros XML ==== Los ficheros XML permiten el intercambio de información entre aplicaciones utilizando para ello un fichero de texto plano al que se le pueden añadir etiquetas para darle significado a cada uno de los valores que se almacenan. Por ejemplo, el siguiente fichero XML podría ser el resultado de volcar una Base de Datos sobre productos de una compañia, de forma que dicha información podría ahora leerse desde otra aplicación e incorporarla. Se puede ver como a parte de los datos de dichos productos (sus nombres, precios, . . .) aparece otra información en forma de etiquetas (entre los caracteres ''<'' y ''>'') que permite dar significado a cada dato almacenado en el fichero. Así, podemos saber de qué estamos hablando y a qué corresponde cada valor. Obviamente, tal y como ocurría con los ficheros de configuración, toda la información se tiene que pasar a texto para poder crear el fichero y reconvertida a su tipo original cuando se cargue de nuevo. Las mismas consideraciones que hemos tenido en cuenta antes nos servirán ahora (ver [[http://adatos.codeandcoke.com/apuntes:ficheros#ficheros_de_configuracion|aqui]]) Cereales 3.45 Colacao 1.45 Agua mineral 1.00 public class Producto { private String nombre; private float precio; public Producto(String nombre, float precio) { this.nombre = nombre; this.precio = precio; } public String getNombre () { return nombre; } public void setNombre (String nombre) { this.nombre = nombre; } public float getPrecio () { return precio; } public void setPrecio (float precio) { this.precio = precio; } } === Escribir ficheros XML === DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); DOMImplementation dom = builder.getDOMImplementation(); //Creo un documento XML y creo el nodo raiz "productos" Document documento = dom.createDocument(null, "xml", null); Element raiz = documento.createElement("productos"); documento.getDocumentElement().appendChild(raiz); Element nodoProducto = null; Element nodoDatos = null ; Text texto = null; //Recorro la lista de productos y creo un subnodo producto por cada elemento for (Producto producto : listaProductos) { nodoProducto = documento.createElement("producto"); raiz.appendChild(nodoProducto); //Por cada dato de cada producto creo un nuevo subnodo nodoDatos = documento.createElement("nombre"); nodoProducto.appendChild(nodoDatos); texto = documento.createTextNode(producto.getNombre()); nodoDatos.appendChild(texto); nodoDatos = documento.createElement("precio"); nodoProducto.appendChild(nodoDatos); texto = documento.createTextNode(producto.getPrecio()); nodoDatos.appendChild(texto); } //Finalizo el documento xml y lo guardo en un fichero de texto Source src = new DOMSource(documento); Result resultado = new StreamResult(new File("fichero.xml")); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(src, resultado); === Leer ficheros XML === DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document documento = builder.parse(new File("fichero.xml")); //Recorro cada uno de los elementos producto para obtener sus campos NodeList productos = documento.getElementsByTagName("producto"); for (int i = 0; i < productos.getLength(); i++) { Node producto = productos.item( i ); Element elemento = ( Element ) producto; System.out.println(elemento.getElementsByTagName("nombre").item(0).getChildNodes().item(0).getNodeValue()); System.out.println(elemento.getElementsByTagName("precio").item(0).getChildNodes().item(0).getNodeValue()); } ===== Ficheros binarios ===== public class Producto implements Serializable { private static final long serialVersionUID = 1L; private String nombre; private float precio; public Producto(String nombre, float precio) { this.nombre = nombre; this.precio = precio; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public float getPrecio() { return precio ; } public void setPrecio(float precio) { this.precio = precio; } } ==== Serialización de objetos ==== //Serializar// es el proceso por el cual un objeto en memoria pasa a transformarse en una estructura que pueda ser almacenada en un fichero (persistencia). Al proceso contrario le llamaremos //deserializar//. Hay que tener en cuenta que durante el proceso de serialización, cada objeto se serializa a un fichero, por lo que si queremos almacenar todos los objetos de una aplicación, la idea más conveniente es tener todos éstos en alguna estructura compleja (y por comodidad dinámica) de forma que sea esta estructura la que serialicemos o deserialicemos para guardar o cargar los objetos de una aplicación. Estructuras como ''ArrayList'', ''Set'' ó ''HashMap'' son clases muy utilizadas para trabajar de esta manera. ===Interface Serializable=== Para poder guardar objetos de una clase directamente en un fichero binario, todas las clases a las que pertenecen los objetos que guarde deben implementar ''Serializable''. Esta interface no tiene métodos, por lo que no necesito sobrescribir nada al implementarla sobre una clase. La mayoría de las clases de la API de Java implementan Serializable. Por ejemplo, si quiero guardar una lista de productos directamente en fichero, tanto la clase ArrayList, como la clase Producto, deben ser Serializable. ArrayList ya lo es, así que debemos implementarla sobre las clases que nosotros creamos (Producto en este caso). === Serializar un objeto === FileOutputStream flujoSalida = null; ObjectOutputStream serializador = null; try { flujoSalida = new FileOutputStream("archivo.dat"); serializador = new ObjectOutputStream(flujoSalida); //Escribo el ArrayList completo serializador.writeObject(listaProductos); } catch (IOException ioe) { . . . } finally { if (serializador != null) try { serializador.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } === Deserializar un objeto === List listaProductos = null; FileInputStream flujoEntrada = null; ObjectInputStream deserializador = null; try { flujoEntrada = new FileInputStream("archivo.dat"); deserializador = new ObjectInputStream(flujoEntrada); //Leo el ArrayList listaProductos = (ArrayList)deserializador.readObject(); } catch (FileNotFoundException fnfe ) { fnfe.printStackTrace(); } catch (ClassNotFoundException cnfe ) { cnfe.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } finally { if (deserializador != null) try { deserializador.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } ===== Estructuras de datos ===== ==== ArrayList ==== [[https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html|ArrayList]] es un //array// redimensionable que implementa el //interface// [[https://docs.oracle.com/javase/8/docs/api/java/util/List.html|List]]. Permite todos los elementos, incluído el valor ''null''. Como es redimensionable, dispone de método para modificar el tamaño del mismo. Para el acceso concurrente con varios hilos, hay que tener en cuenta que esta estructura de datos no está sincronizada y su acceso deberá estarlo de forma externa. También se podría emplear la clase [[https://docs.oracle.com/javase/8/docs/api/java/util/Vector.html|Vector]] que es igual que ésta pero está ya sincronizada. === Operaciones más comunes === * Añadir un elemento List listaCadenas = new ArrayList<>(); String nuevaCadena = "Esto es una cadena"; listaCadenas.add(nuevaCadena); * Añadir un elemento en una posición determinada int posicion = 3; listaCadenas.add(3, nuevaCadena); * Reemplazar el elemento que ocupa una posición determinada int posicion = 3; String otraCadena = "Esto es otra cadena"; listaCadenas.set(3, otraCadena); * Eliminar un elemento int posicion = 3; listaCadenas.remove(posicion); * Obtener la referencia a un elemento int posicion = 3; String unaCadena = listaCadenas.get(posicion); * Conocer el número de elementos System.out.println("El ArrayList tiene " + listaCadenas.size() + " elementos"); * Eliminar un elemento concreto de la lista listaCadenas.remove(objeto); * Eliminar todos los elementos de la lista listaCadenas.clear(); * Añadir todos los elementos de otra lista List otraListaCadenas = new ArrayList<>(); . . . . . . // Se puede pasar como parámetro un objeto que implemente el interfaz Collection listaCadenas.addAll(otraListaCadenas); * Comprobar si esta vacía if (listaCadenas.isEmpty()) { System.out.println("La lista no tiene elementos"); } * Obtener un array (estático) con todos los elementos de la lista String[] arrayCadenas = listaCadenas.toArray(); ==== HashMap ==== [[https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html|HashMap]] es una tabla //hash// que implementa el //interface// [[https://docs.oracle.com/javase/8/docs/api/java/util/Map.html|Map]]. Permite el valor ''null'' tanto para valores como para claves. Al contrario que [[https://docs.oracle.com/javase/8/docs/api/java/util/TreeSet.html|TreeSet]] no garantiza el orden de los elementos o que éste se mantenga. Hay que tener en cuenta que el acceso a esta implementación no está sincronizado, pero existe una implementación similar y sincronizada, que es la clase [[https://docs.oracle.com/javase/8/docs/api/java/util/Hashtable.html|Hashtable]]. === Operaciones más comunes === * Añadir un elemento (pareja clave-valor) Map libros = new HashMap<,>(); . . . Libro libro = new Libro(); libro.setTitulo("Secuestrado"); libro.setAutor("Robert Louis Stevenson"); . . . libros.add(libro.getTitulo(), libro); * Obtener un elemento String tituloLibro = "Secuestrado"; Libro esteLibro = libros.get(titulo); * Comprobar si existe una clave String titulo = "Secuestrado"; if (libros.containsKey(titulo)) { System.out.println("El libro con el título " + titulo + " existe en tu colección"); } * Comprobar si esta vacío if (libros.isEmpty()) { System.out.println("Tu colección de libros está vacía"); } * Eliminar todos los elementos libros.clear(); * Eliminar un elemento (por clave) String titulo = "Secuestrado"; libros.remove(titulo); * Obtener una ''Collection'' con todos los valores Collection coleccionLibros = libros.values(); // ArrayList, TreeSet y LinkedList implementan el interface Collection * Obtener un ''Set'' con todas las claves Set titulos = libros.keySet(); // TreeSet y HashSet implementan el interface Set * Comprobar el tamaño del ''Map'' System.out.println("Tienes " + libros.size() + " libros en tu colección"); * Concatenar todos los elementos de otro ''Map'' Map masLibros = new HashMap<,>(); . . . . . . libros.putAll(masLibros); ==== TreeSet ==== [[https://docs.oracle.com/javase/8/docs/api/java/util/TreeSet.html|TreeSet]] es una colección que mantiene los elementos ordenados de forma natural o bien a través de un [[https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html|Comparator]]. === Operaciones más comunes === * Añadir un elemento Set cadenas = new TreeSet<>(); String unaCadena = "Esto es una cadena"; cadenas.add(unaCadena); * Añadir todos los elementos de otra ''Collection'' List masCadenas = new ArrayList<>(); . . . cadenas.addAll(masCadenas); * Obtener un elemento * Eliminar todos los elementos cadenas.clear(); * Comprueba si existe un elemento en la lista String otraCadena = "Esto es una cadena"; if (cadenas.contains(otraCadena)) { System.out.println("La cadena existe"); } * Obtener el primer elemento String primeraCadena = cadenas.first(); * Obtener y eliminar el primer elemento String primeraCadena = cadenas.pollFirst(); * Obtener el último elemento String ultimaCadena = cadenas.last(); * Obtener y eliminar el último elemento String ultimaCadena = cadenas.pollLast(); * Comprobar si esta vacío if (cadenas.isEmpty()) { System.out.println("El Set de cadenas esta vacío"); } * Comprobar el número de elementos System.out.println("Tienes " + cadenas.size() + " cadenas"); * Obtiene la parte del ''Set'' cuyos elementos son menores que uno pasado por parámetro String cadenaLimite = "Una cadena"; SortedSet cadenasMenores = cadenas.headSet(cadenaLimite); // SortedSet es el interface que implementa TreeSet * Obtiene la parte del ''Set'' cuyos elementos están entre dos determinados String cadenaMenor = "Una cadena"; String cadenaMayor = "Más cadena"; SortedSet cadenasMenores = cadenas.subSet(cadenaMenor, cadenaMayor); // SortedSet es el interface que implementa TreeSet ==== TreeMap ==== [[https://docs.oracle.com/javase/8/docs/api/java/util/TreeMap.html|TreeMap]] es una colección de pares //clave-valor// como [[https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html|HashMap]] pero mantiene los elementos ordenados por su clave de forma natural o bien a través de un [[https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html|Comparator]]. === Operaciones más comunes === * Añadir un elemento (pareja clave-valor) Map libros = new TreeMap<,>(); . . . Libro libro = new Libro(); libro.setTitulo("Secuestrado"); libro.setAutor("Robert Louis Stevenson"); . . . libros.put(libro.getTitulo(), libro); * Obtener un elemento String tituloLibro = "Secuestrado"; Libro esteLibro = libros.get(titulo); * Comprobar si existe una clave String titulo = "Secuestrado"; if (libros.containsKey(titulo)) { System.out.println("El libro con el título " + titulo + " existe en tu colección"); } * Comprobar si esta vacío if (libros.isEmpty()) { System.out.println("Tu colección de libros está vacía"); } * Eliminar todos los elementos libros.clear(); * Eliminar un elemento (por clave) String titulo = "Secuestrado"; libros.remove(titulo); * Obtener una ''Collection'' con todos los valores Collection coleccionLibros = libros.values(); // ArrayList, TreeSet y LinkedList implementan el interface Collection * Obtener un ''Set'' con todas las claves Set titulos = libros.keySet(); // TreeSet y HashSet implementan el interface Set * Comprobar el tamaño del ''Map'' System.out.println("Tienes " + libros.size() + " libros en tu colección"); * Concatenar todos los elementos de otro ''Map'' Map masLibros = new TreeMap<,>(); . . . . . . libros.putAll(masLibros); ==== LinkedList ==== [[https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html|LinkedList]] Es una lista doblemente enlazada que implementa el interfaz [[https://docs.oracle.com/javase/8/docs/api/java/util/List.html|List]] === Operaciones más comunes === * Añadir un elemento List listaLibros = new LinkedList<>(); . . . Libro libro = new Libro(); libro.setTitulo("Secuestrado"); libro.setAutor("Robert Louis Stevenson"); . . . listaLibros.add(libro); * Añadir un elemento al principio List listaLibros = new LinkedList<>(); . . . Libro libro = new Libro(); libro.setTitulo("Secuestrado"); libro.setAutor("Robert Louis Stevenson"); . . . listaLibros.addFirst(libro); * Añadir un elemento al final List listaLibros = new LinkedList<>(); . . . Libro libro = new Libro(); libro.setTitulo("Secuestrado"); libro.setAutor("Robert Louis Stevenson"); . . . listaLibros.addLast(libro); * Añadir toda una colección al final List listaLibros = new LinkedList<>(); List otraListaDeLibros = new ArrayList<>(); . . . // Podemos añadir cualquier objeto que implemente el interface Collection listaLibros.addAll(otraListaDeLibros); * Obtener un elemento Libro unLibro = listaLibros.get(4); * Obtener el primer elemento Libro unLibro = listaLibros.getFirst(); * Eliminar (y obtener) el primer elemento Libro primerLibro = listaLibros.removeFirst(); * Eliminar (y obtener) el último elemento Libro ultimoLibro = listaLibros.removeLast(); * Eliminar (y obtener) un elemento Libro unLibro = listaLibros.remove(3); * Eliminar todos los elementos listaLibros.clear(); * Obtener el número de elementos de la lista System.out.println("Esta lista tiene " + listaLibros.size() + " libros"); * Obtener un array (estático) con todos los elementos de la lista Libro[] arrayLibros = listaLibros.toArray(); ==== Comparativa de la JFC (Java Collection Framework) ==== {{ apuntes:comparison.jpg }} > //JCF//: [[https://docs.oracle.com/javase/8/docs/technotes/guides/collections/index.html|Java Collection Framework]] ===== Patrón Modelo-Vista-Controlador ===== El patrón Modelo-Vista-Controlador o MVC (Model-View-Controller en inglés) es un patrón de diseño que busca separar, en una aplicación con GUI, la lógica de la presentación de los datos facilitando así la reutilización de código y el mantenimiento de la aplicación. {{ apuntes:mvc.jpg }} > //MVC//: Model-View-Controller ==== Ejemplo de aplicación MVC ==== A continuación se muestra el código completo de una pequeña aplicación con GUI de gestión con las operaciones más básicas (Alta, Modificar, Baja, Búsqueda) gestionada mediante colecciones Java. Según el patrón MVC, la aplicación quedará separada en tres partes: //Model//, //View// y //Controller//. A esas 3 partes habrá que añadir las clases //POJOs (Plain Old Java Object)// que son las clases que representan a los objetos o elementos que se gestionan en la aplicación. Contendrán atributos, constructores, getters, setters y los métodos que proceda implementar para cada clase. En este caso hay una única clase //POJO// que es ''Animal.java'' puesto que la aplicación permite gestionar una base de datos de animales, en este caso mediante colecciones Java de forma que queda como ejercicio muy interesante transformar esta aplicación en una que gestione la información con Bases de Datos (MySQL ó PostgreSQL). Será entonces cuando se aprecie la separación Model-View-Controller y la reutilización real de código entre ambas versiones de esta aplicación. {{ gestion_animales.png }} /** * Clase que representa a un Animal */ public class Animal { private String nombre; private String raza; private String caracteristicas; private float peso; // Constructores, getters y setters // Otros métodos (cuando proceda) } La clase ''Animal'' representa a los animales que vamos a gestionar y contendrá tantos atributos como características queramos almacenar del mismo. Además haremos los correspondientes constructores, //getters// y //setters//. Si procede, podemos añadir métodos que realicen operaciones sobre la clase. > [[https://bitbucket.org/sfaci/java-ficheros/src/9625f045ba0e455e8f879a81b9535a83445ca201/GestionAnimales/src/org/sfaci/gestionanimales/base/Animal.java?at=master&fileviewer=file-view-default|Animal.java]] (Todo el código) /** * Clase que contiene la GUI de la Aplicación */ public class Ventana { private JPanel panel; JTextField tfNombre; JTextField tfRaza; // Resto de controles swing . . . public Ventana() { JFrame frame = new JFrame("Ventana"); frame.setContentPane(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } } La clase ''Ventana'' contendrá exclusivamente el diseño de la ventana y en el constructor la inicialización de la misma según convenga en cada caso. Ni siquiera los //Listeners// aparecerán en esta ventana. Por comodidad, no utilizaremos ningún modificador de accesibilidad para los componentes de la ventana, puesto que deberemos poder acceder a ellos desde el //Controller//. Otra manera será proporcionar //getters// para todos ellos y así podremos declararlos como privados o lo que mejor convenga. > [[https://bitbucket.org/sfaci/java-ficheros/src/9625f045ba0e455e8f879a81b9535a83445ca201/GestionAnimales/src/org/sfaci/gestionanimales/gui/Ventana.java?at=master&fileviewer=file-view-default|Ventana.java]] (Todo el código) /** * Controlador para la ventana * Recibe los inputs del usuario desde la ventana (View) y se * comunica con el Model si es necesario */ public class VentanaController implements ActionListener, KeyListener { private VentanaModel model; private Ventana view; private int posicion; public VentanaController(VentanaModel model, Ventana view) { this.model = model; this.view = view; anadirActionListener(this); anadirKeyListener(this); posicion = 0; } @Override public void actionPerformed(ActionEvent event) { String actionCommand = event.getActionCommand(); Animal animal = null; switch (actionCommand) { case "Nuevo": view.tfNombre.setText(""); view.tfNombre.setEditable(true); view.tfCaracteristicas.setText(""); view.tfCaracteristicas.setEditable(true); // Limpiar/Resetear resto de componentes . . . . . . view.btGuardar.setEnabled(true); break; case "Guardar": if (view.tfNombre.getText().equals("")) { Util.mensajeError("El nombre es un campo obligatorio", "Nuevo Animal"); return; } animal = new Animal(); animal.setNombre(view.tfNombre.getText()); animal.setRaza(view.tfRaza.getText()); animal.setCaracteristicas(view.tfCaracteristicas.getText()); animal.setPeso(Float.parseFloat(view.tfPeso.getText())); model.guardar(animal); view.btGuardar.setEnabled(false); break; case "Modificar": animal = new Animal(); animal.setNombre(view.tfNombre.getText()); animal.setRaza(view.tfRaza.getText()); animal.setCaracteristicas(view.tfCaracteristicas.getText()); animal.setPeso(Float.parseFloat(view.tfPeso.getText())); model.modificar(animal); break; case "Cancelar": view.tfNombre.setEditable(false); view.tfCaracteristicas.setEditable(false); . . . animal = model.getActual(); cargar(animal); view.btGuardar.setEnabled(false); break; case "Eliminar": if (JOptionPane.showConfirmDialog(null, "¿Está seguro?", "Eliminar", JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) return; model.eliminar(); animal = model.getActual(); cargar(animal); break; case "Buscar": animal = model.buscar(view.tfBusqueda.getText()); if (animal == null) { Util.mensajeInformacion("No se ha encontrado ningún animal con ese nombre", "Buscar"); return; } cargar(animal); break; . . . . . . default: break; } } . . . . . . @Override public void keyReleased(KeyEvent e) { if (e.getSource() == view.tfBusqueda) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { view.btBuscar.doClick(); } } } /** * Carga los datos de un animal en la vista * @param animal */ private void cargar(Animal animal) { if (animal == null) return; view.tfNombre.setText(animal.getNombre()); view.tfCaracteristicas.setText(animal.getCaracteristicas()); . . . . . . } . . . /** * Añade el Listener a todos los controles de la View */ private void anadirActionListener(ActionListener listener) { view.btNuevo.addActionListener(listener); view.btGuardar.addActionListener(listener); . . . . . . } } La clase ''VentanaController'' contiene el //Controller// de la ventana, que hará de intermediario entre la capa //View// que es la GUI de la aplicación y el //Model// que contiene toda la lógica y el acceso a los datos. Además, se encarga de gestionar los eventos de cada componente de la GUI (implementando los //Listeners// necesarios). También se incluyen en este componente todas las validaciones de datos relacionadas con el //input// del usuario y la visualización de los mensajes de errores que correspondan. > [[https://bitbucket.org/sfaci/java-ficheros/src/9625f045ba0e455e8f879a81b9535a83445ca201/GestionAnimales/src/org/sfaci/gestionanimales/gui/VentanaController.java?at=master|VentanaController.java]] (Todo el código) /** * Modelo para la ventana */ public class VentanaModel { private ArrayList listaAnimales; private int posicion; public VentanaModel() { listaAnimales = new ArrayList<>(); posicion = 0; } /** * Guarda un animal en la lista * @param animal */ public void guardar(Animal animal) { listaAnimales.add(animal); posicion++; } /** * Modifica los datos del animal actual * @param animalModificado */ public void modificar(Animal animalModificado) { Animal animal = listaAnimales.get(posicion); animal.setNombre(animalModificado.getNombre()); animal.setCaracteristicas(animalModificado.getCaracteristicas()); animal.setRaza(animalModificado.getRaza()); animal.setPeso(animalModificado.getPeso()); } /** * Elimina el animal actual */ public void eliminar() { listaAnimales.remove(posicion); } public Animal getActual() { return listaAnimales.get(posicion); } /** * Busca un animal en la lista * @param nombre El nombre del animal * @return El animal o null si no se ha encontrado nada */ public Animal buscar(String nombre) { for (Animal animal : listaAnimales) { if (animal.getNombre().equals(nombre)) { return animal; } } return null; } /** * Obtiene el animal que está en primera posición en la lista * @return */ public Animal getPrimero() { posicion = 0; return listaAnimales.get(posicion); } /** * Obtiene el animal que está en la posición anterior a la actual * @return */ public Animal getAnterior() { if (posicion == 0) return null; posicion--; return listaAnimales.get(posicion); } /** * Obtiene el animal que está en la posición siguiente a la actual * @return */ public Animal getSiguiente() { if (posicion == listaAnimales.size() - 1) return null; posicion++; return listaAnimales.get(posicion); } /** * Obtiene el animal que está en la última posición de la lista * @return */ public Animal getUltimo() { posicion = listaAnimales.size() - 1; return listaAnimales.get(posicion); } } La clase ''VentanaModel'', puesto que es el //Model//, contendrá todos los métodos que proporcionan el acceso a los datos y la lógica de la aplicación. En este caso contiene todos los métodos que permite obtener la información de los animales, los resultados de las búsquedas y también se llevan a cabo las operaciones de inserción, modificación y eliminación. En este caso todas las operaciones se realizan sobre una colección ''ArrayList''. Si quisiéramos hacer que la aplicación utilizara una Base de Datos como motor de almacenamiento sólo tendríamos que modificar este componente de la aplicación haciendo que los mismos métodos (incluso respetando su declaración: nombre, tipo devuelto y parámetros) utilicen una Base de Datos para realizar todas las operaciones. No sería necesario realizar ninguna modificación en el resto de la aplicación. > [[https://bitbucket.org/sfaci/java-ficheros/src/9625f045ba0e455e8f879a81b9535a83445ca201/GestionAnimales/src/org/sfaci/gestionanimales/gui/VentanaModel.java?at=master|VentanaModel]] (Todo el código) /** * Aplicación para la gestión de animales utilizando el patrón MVC * * Clase principal, que solamente lanza la aplicación */ public class Aplicacion { public static void main(String args[]) { Ventana view = new Ventana(); VentanaModel model = new VentanaModel(); VentanaController controller = new VentanaController(model, view); } } En la clase ''Aplicacion'', que contiene el método ''main'' que lanzará la aplicación, simplemente tenemos que instanciar los tres componentes de nuestra aplicación (//Model//, //View// y //Controller//) y "conectarlos" mediante el último. {{ vimeo>296086288?medium }} ---- ===== Ejercicios ===== - Escribe un programa capaz de registrar información sobre los alumnos de un Colegio (nombre, apellidos, fecha de nacimiento y ciclo que estudian) y listarla en un ''JList''. La información que se muestra en la lista se mantendrá actualizada en todo momento. Diseña la aplicación según el patrón de diseño MVC (Model-View-Controller){{ colegio.png }}\\ - Añade al programa anterior los botones necesarios para poder "navegar" (ir al primero, anterior, siguiente y último) por los registros de la lista \\ \\ - Añadir un botón al primer ejercicio para que el usuario guarde la información de la lista en un fichero. El usuario tendrá que escoger cada vez el fichero donde quiere almacenar la información \\ \\ - Crea una aplicación que sea capaz de exportar a XML la información de una receta (nombre, descripción, ingredientes) {{ recetario.png }} \\ - Crea una aplicación que sea capaz de importar un fichero XML con información de un catálogo de películas (titulo, fecha, genero, sinopsis y actores principales). Diseña la GUI para que se pueda visualizar toda la información de cada una de las películas importadas {{ imdb.png }}\\ - Crea una aplicación que sirva de Bloc de Notas con las funciones de //Nuevo//, //Guardar// y //Guardar como//. Diseña la aplicación según el patrón de diseño MVC (Model-View-Controller) ---- ===== Proyectos de ejemplo ===== Los proyectos de ejemplo con contenidos de esta unidad están disponibles en el [[https://bitbucket.org/fvaldeon/accesodatosud1/src/|repositorio de proyectos UD1]]. Para manejaros con Git recordad que tenéis una serie de videotutoriales en la sección [[extra:referencias|Referencias]] Por otra parte, para el tema de [[http://programacion.codeandcoke.com/doku.php?id=bloque4:dialogos|creación y uso de distintos cuadros de diálogo]], podeis consultar la sección de los apuntes de programación. También los apuntes sobre funcionamiento de la mayoría de [[http://programacion.codeandcoke.com/doku.php?id=bloque4:swing|componentes de las librerías de Swing]]. También, los proyectos de ejemplo de estos apuntes están en el [[http://www.bitbucket.org/sfaci/java-ficheros|repositorio java-ficheros]] de BitBucket del profesor Santiago Faci. Podéis acceder tanto al código como a las descargas, a continuación: * [[https://bitbucket.org/sfaci/java-ficheros/downloads/FicherosXML.zip|Ficheros XML]] Leer y escribir ficheros XML * [[https://bitbucket.org/sfaci/java-ficheros/downloads/SerializarObjetos.zip|Serializar Objetos]] Serializar Objetos en Java * [[https://bitbucket.org/sfaci/java-ficheros/downloads/SerializarObjetos2.zip|Serializar Objetos II]] Serializar objetos con opción de búsqueda * [[https://bitbucket.org/sfaci/java-ficheros/downloads/SerializarObjetosGUI.zip|Serializar Objetos GUI]] Serializar objetos con GUI * [[https://bitbucket.org/sfaci/java-ficheros/downloads/GestionAnimales.zip|Gestión de Animales]] Demo de aplicación de gestión (con GUI) utilizando MVC (Model View Controller) * [[https://bitbucket.org/sfaci/java-ficheros/downloads/DamGames.zip|Gestión de Videojuegos]] Demo de aplicación de gestión (con GUI) utilizando componentes ===== Prácticas ===== * **Práctica 1.1** Desarrollar una aplicación que almacene datos en un fichero ---- (c) {{date> %Y}} Santiago Faci y Fernando Valdeón