miércoles, 30 de julio de 2008

Aplicaciones Java (VI): Salida de consola

A veces es conveniente poder escribir trazas para saber lo que está haciendo nuestro programa en cada momento, por ejemplo:

10:24:25 - Descargando fichero "342314.png"
10:24:29 - Descargando fichero "342315.png"
10:24:40 - Descargando fichero "342316.png"
...

En aplicaciones de consola (las que ejecutamos por línea de comandos), crear trazas de este tipo es trivial:

System.out.println("Fichero descargado");

Sin embargo, en aplicaciones gráficas no veremos el intérprete de comandos, y por tanto no podremos acceder a la salida de consola.

Una opción es escribir en un fichero de log, para lo que podemos utilizar una librería del estilo de log4j. Sin embargo, un fichero de log es más problemático para aplicaciones que queremos ejecutar desde la web, sin instalar nada, y no está integrado en el interfaz de la aplicación, sino que hay que acceder a él por separado.

Vamos a crear una clase que nos permitirá, con el mismo código, escribr trazas en stdout, o en un componente de tipo JTextArea.

Esta clase tendrá el modificador final, de modo que no será necesario instanciarla. De este modo, desde cualquier punto de nuestro programa, podremos ejecutar:

Console.println("Fichero descargado");

Que como vemos es muy sencillo y limpio.

En nuestra aplicación únicamente tendremos que crear el JTextArea y asignarlo al objeto Console.

JTextArea consoleOutput = new JTextArea(20, 40);
consoleOutput.setEditable(false);
Console.useJTextComponent(consoleOutput);

Si no reasignamos la salida de la consola a un JTextArea, la salida por defecto será stdout.

Esta es la implementación de la clase Console:

package org.dreamcoder.SwingSample;

import javax.swing.text.PlainDocument;
import javax.swing.text.JTextComponent;
import java.io.PrintStream;

public final class Console
{
 private static final long  serialVersionUID = 3;
 
 private static JTextComponent textField;
 private static PrintStream    stream = System.out; // by default we will output to stdout

    private Console()
    {
        throw new AssertionError("I am a static class. Don't instantiate me.");
    }
    
 public static void useJTextComponent(JTextComponent newTextArea)
 {
  textField = newTextArea;
 }

 public static void usePrintStream(PrintStream newStream)
 {
  stream = newStream;
 }

 public static void println(String str)
 {
        if (textField != null)
        {
            PlainDocument doc = (PlainDocument)textField.getDocument();
            
            try {
                doc.insertString(doc.getLength(), str+"\n", null);
            } catch (javax.swing.text.BadLocationException e) {}

            textField.setCaretPosition(doc.getLength());
        }
        else
        {
            stream.println(str);
        }
    }
}

Vemos en la implementación del método println que puede funcionar de 2 modos: con un stream (como System.out o un fichero, y con un componente que contenga texto). Para este último modo, obtenemos el documento (PlainDocument) correspondiente al componente, y trabajaremos con este documento. El documento es la parte del componente que contiene los datos, independientemente de la representación gráfica.

A partir de esta clase, si nuestra aplicación lo requiere podemos crear una variación que nos permita instanciar diferentes objetos de esta clase, cada uno escribiendo en un JTextArea o PrintStream diferente. Por ejemplo, podríamos tener 2 consolas distintas basadas en un JTextArea, y una tercera salida que escriba en stdout o en un fichero, socket, etc.

Material

El código fuente mostrando el uso de estos componentes se puede descargar bajo licencia GPLv3:
Download

Ejecutar ejemplo, como applet o como Java Web Start (recomendado):
Start Applet Launch via Java Web Start

No hay comentarios: