Sesión 10

En esta sesión vamos a hacer un ejercicio práctico que englobe varios de los conceptos vistos hasta ahora.

1. Vamos a construir una aplicación que permita definir distintos tipos de figuras geométricas, y poderlas leer y guardar en ficheros. Antes de explicar los pasos a seguir, daremos un rápido vistazo a la estructura de la aplicación, para entender qué ficheros y directorios tenéis en la plantilla.

En la plantilla tenemos dos paquetes de clases:

Finalmente, tenemos una clase principal en el directorio raíz, llamada AplicGeom, que leerá las figuras que haya en un fichero que se le pase como parámetro, y mostrará un menú de opciones para que el usuario pueda:

  1. Crear una nueva figura geométrica
  2. Borrar una figura geométrica de la lista existente
  3. Guardar la lista de figuras actual en el fichero de entrada
  4. Salir del programa

Veremos ahora qué pasos seguir para construir todo esto.

  1. En primer lugar, construiremos el paquete geom. La clase Figura ya está completa, así que rellenaremos las otras:
    1. La clase Circulo deberá tener dos campos enteros x, y, que indiquen la posición de su centro, y luego un campo entero radio, que indique la longitud de su radio. Haremos un constructor que tome estos tres campos y los asigne, y luego métodos getXXX y setXXX para obtener y cambiar el valor de cada campo. Deberemos rellenar también el método imprime, ya que es abstracto en la superclase. Dicho método devolverá una cadena con información de los campos de la figura:
      package geom;
      
      public class Circulo extends Figura implements java.io.Serializable
      {
      	int x;
      	int y;
      	int radio;
      	
      	public Circulo(int x, int y, int radio)
      	{
      		...
      	}
      	
      	public int getX()
      	{
      		...
      	}
      	
      	public int getY()
      	{
      		...
      	}
      	
      	public int getRadio()
      	{
      		...
      	}
      	
      	public void setX(int x)
      	{
      		...
      	}
      	
      	public void setY(int y)
      	{
      		...
      	}
      	
      	public void setRadio(int radio)
      	{
      		...
      	}
      
      	public String imprime()
      	{
      		return "CIRCULO: (" + x + ", " + y + "), r = " + radio;
      	}
      }
    2. De forma similar construimos las clases Rectangulo y Linea. Estas clases tendrán cada una cuatro campos: x1, y1, x2 e y2. En el caso del Rectangulo, (x1, y1) indican la posición del vértice superior izquierdo, y (x2, y2) la del vértice inferior derecho. En el caso de la Linea, (x1, y1) indican un extremo de la línea, y (x2, y2) el otro extremo. 
      ...
      public class Rectangulo extends Figura implements java.io.Serializable
      {
      	int x1;
      	int y1;
      	int x2;
      	int y2;
      ...
      ...
      public class Linea extends Figura implements java.io.Serializable
      {
      	int x1;
      	int y1;
      	int x2;
      	int y2;
      ...

      Recordad definir también el método imprime para que saque una cadena de texto con información relativa a cada figura.

      En el caso del Rectangulo, sería algo como:

      "RECTANGULO: (" + x1 + ", " + y1 + ") - (" + x2 + ", " + y2 + ")"

      Y para la Linea:

      "LINEA: (" + x1 + ", " + y1 + ") - (" + x2 + ", " + y2 + ")"

      NOTA IMPORTANTE: Observad que tanto la clase padre Figura como todas las subclases definidas son Serializables (implementan la interfaz Serializable). Esto nos permitirá después guardarlas y leerlas de ficheros como objetos completos. Recordad también poner la directiva package al principio del fichero, indicando el paquete al que pertenecen las clases.

  2. Completaremos ahora el paquete io.
    1. Comenzamos por la clase EntradaTeclado. Simplemente debe recoger los datos que el usuario introduzca por teclado, así que declararemos un campo de tipo BufferedReader:
      package io;
      
      import java.io.*;
      
      public class EntradaTeclado
      {
      	BufferedReader in = null;
      	...

      Después, en el constructor hacemos que dicho buffer lea de la entrada estándar (System.in):

      	...
      	public EntradaTeclado()
      	{
      		in = new BufferedReader(new InputStreamReader(System.in));
      	}

      Finalmente, definimos un método leeTeclado que devuelva una cadena con cada línea que el usuario ha escrito hasta pulsar Intro. Esta cadena la utilizaremos después desde el programa principal, para recoger todas las órdenes del usuario (capturamos las excepciones necesarias, y devolvemos null si se produce algún error):

      	...
      	public String leeTeclado()
      	{
      		try
      		{
      			return in.readLine();
      		} catch (Exception e) {
      			return null;
      		}
      	}
      }
    2. Por otro lado, completamos la clase IOFiguras. Esta clase tendrá dos métodos estáticos (para llamarlos sin tener que crear un objeto de la clase): uno servirá para leer un conjunto de figuras de un fichero, y el otro para guardarlas en fichero.
      package io;
      
      import geom.*;
      import java.io.*;
      import java.util.*;
      
      public class IOFiguras
      {
      	public static Figura[] leeFiguras(String fichero)
      	{
      		...
      	}
      	public static void guardaFiguras(Figura[] figuras, String fichero)
      	{
      		...
      	}
      }
      1. Para el método leeFiguras, haremos que abra un ObjectInputStream contra el fichero fichero, y que vaya leyendo objetos de tipo Figura y metiéndolos en una lista (ArrayList), hasta que salte la excepción que indique el fin de fichero. Después, construiremos un array con la lista, y lo devolveremos. En el caso de que no haya elementos en la lista, devolveremos null.
        public static Figura[] leeFiguras(String fichero)
        {
        	ArrayList alAux = new ArrayList();
        	Figura f;
        
        	try
        	{
        		ObjectInputStream oin = 
        		  new ObjectInputStream (new FileInputStream(fichero));
        
        		... // leer figuras del fichero

        oin.close(); } catch (Exception e) { // Se pueden producir 2 excepciones: // FileNotFoundException si no encuentra el fichero // IOException cuando llegue a fin de fichero // DA IGUAL QUE SE PRODUZCA UNA U OTRA, // LA CAPTURAMOS Y NO HACEMOS NADA MAS } // Si no había fichero, o no tenía figuras, la lista estará // vacía (se inicializa al principio del método, pero no llega // a entrar en el "while"). Entonces devolvemos null if (alAux.size() == 0) return null; // Si había figuras, devolvemos un array con ellas return((Figura[])(alAux.toArray(new Figura[0]))); }

        Observa que leemos objetos de tipo Figura (genéricos), sin discriminar si son líneas, círculos o rectángulos. El objeto se recupera tal cual, y del tipo que sea, aunque se trate como una figura genérica. Observa también que no hay forma de detectar el final del fichero. Se lanzará una IOException cuando lleguemos. Así que hay que meter la lectura de objetos en un bucle (infinito), y el bucle en un try, de forma que cuando llegue al final del fichero lanzará la excepción y saldremos del bucle, con todo el fichero ya leído. Por último, observa que no pasa nada si el fichero no existe, o si se lanza cualquier excepción. El método irá añadiendo figuras conforme las vaya leyendo: si no hay fichero, o no hay figuras en él, la lista quedará vacía (nunca entrará en el "while"), y se devolverá null. Si hay figuras, se irán colocando en la lista y se devolverá la lista al final. Después se trata de tomar lo que devuelva la llamada al método y hacer lo que corresponda:

        Figura[] fig = IOFiguras.leeFiguras(nombre_fichero);
        if (fig != null)
        {
        	// ... Procesar el array de figuras como se necesite
        	// Por ejemplo, para meterlas en una lista:
        	ArrayList figuras = new ArrayList();
        	for (int i = 0; i < fig.length; i++)
        		figuras.add(fig[i]);
        } else {
        	// ... No hay figuras que procesar
        }

        Captura las excepciones adecuadas para poderlo compilar. No es necesario que hagas nada en el bloque catch de la captura.

      2. Para el método de guardado, guardaFiguras, abrimos un ObjectOutputStream contra el fichero fichero, luego vamos recorriendo el array figuras y metiendo cada una en el fichero, con un writeObject:
        public static void guardaFiguras(Figura[] figuras, String fichero)
        {
        	try
        	{
        		ObjectOutputStream oout = 
        		   new ObjectOutputStream (new FileOutputStream(fichero));
        
        		... // guardar figuras 	
        oout.close(); } catch (Exception e) {} }

        Captura las excepciones adecuadas, para poderlo compilar. No es necesario que hagas nada en el bloque catch de la captura.

  3. Lo que nos queda es completar el programa principal AplicGeom. Veréis que la clase tiene un campo de tipo EntradaTeclado para leer las órdenes del usuario, otro para guardar el nombre del fichero con que trabajar, y otro que es una lista (ArrayList) con las figuras que haya en cada momento.

    Tenemos también un constructor al que se le pasa el nombre del fichero y lo asigna. Deberemos también inicializar la EntradaTeclado en el constructor, y leer del fichero las figuras (ayudándonos de la clase IOFiguras) y guardarlas en la lista:
    public AplicGeom(String fichero)
    {
    	this.fichero = fichero;
    	et = new EntradaTeclado();
    	figuras = new ArrayList();
    	Figura[] fig = IOFiguras.leeFiguras(fichero);
    
    	// ... Aquí iría el resto del código: un bucle para 
    	// recorrer las figuras del array "fig" (si no es null)
    	// y meterlas en la lista "figuras"
    }

    Después vemos que hay un método main que recoge el nombre del fichero de los parámetros de entrada, y crea un objeto de tipo AplicGeom con él. Finalmente, llama al método iniciar de la clase, que está vacío, pero deberá contener toda la lógica del programa. Deberéis completarlo para que haga una iteración continua en la que:

  4. Compila y ejecuta la aplicación completa, verificando que funciona correctamente.
  5. Una vez finalizado el ejercicio, y visto la forma en que está estructurado...

PARA ENTREGAR

ENTREGA FINAL DEL BLOQUE 2