Archivo de la categoría Java/Programación

Consejos para programar con Swing

Miércoles, 25 de Abril de 2007

Un listado de consejos y buenas prácticas para desarrollar en Swing, , por cortesía de Chuidang.
No son patrones, no son trucos, son consejos desde el dolor de haber sufrido durante tiempo fruto de la experiencia y del día a día. No los pases por alto si te vas a meter de cabeza con Swing.

  • En la aplicación sólo debe haber un único JFrame, correspondiente a la aplicación principal. Todas las ventanas secundarias deben ser JDialog. Todas las ventanas secundarias deben tener una ventana padre, que es a partir de la cual se despliega. Es decir, todos los JDialog secundarios deben tener como padre al JFrame principal. Si desde un JDialog se va a visualizar otro, este segundo debe tener como padre al primero, y así sucesivamente.
  • Evita en lo posible los JDialog modales, o ten muy en cuenta su jerarquía de padres. El primer JDialog modal no tiene problemas si le pones su padre adecuadamente. Si tienes un JDialog modal visible, no muestres otro JDialog secundario, salvo que también sea modal y sea hijo del anterior. Si pones visibles a la vez dos JDialog modales y no son el uno hijo del otro, tendrás problemas al intentar escribir en ellos o cerrarlos.
  • Nunca heredes de JFrame o JDialog o JApplet para hacer tus ventanas. Hazlo siempre de un componente que no sea ventana y que no te limite. Si tus ventanas heredan de JPanel, podrás ponerlas siempre que quieras dentro de un JFrame, un JDialog, un JInternalFrame, un JApplet o incluso incrustarlas en otro JPanel. Si tu ventana hereda de JFrame, está condenada a ser un JFrame toda su vida.
  • Reaprovecha las ventanas, no se las dejes al recolector de basura. Si un botón, al apretarlo, visualiza un JDialog, no hagas un new de JDialog cada vez que pulsas el botón. Es mejor hacer sólo un new la primera vez y guardarselo. En las siguientes veces bastará con hacer setVisible(true) y setVisible(false). Para que el recolector de basura libere una ventana, además de lo habitual, hay como minimo que llamar al método dispose() de dicha ventana  -cosa que mucha gente no sabe- , para que el sistema de eventos de teclado y ratón eliminen todas las referencias que tienen a ella. De todas formas, incluso así no tengo muy claro que los JDialog se liberen siempre y, desde luego, en versiones anteriores de java, los JFrame NUNCA se liberaban. La excusa de SUN es que como sólo debía haber un JFrame principal, no tenía sentido liberarlo.
  • Los layouts para situar componentes no son tan complicados, sólo hay que ponerse a ello. No uses el layout null, ya que tu ventana no será redimensionable y puedes tener problemas si cambia la fuente de letra, si tu programa se ejecuta en otro sistema operativo, se cambia el look & feel, etc.
  • Una vez que sepas los layouts simples, tenderás a hacer ventanas grandes a base de anidar muchos JPanel que a su vez tienen dentro JPanel que su vez tienen dentro JPanel, todos ellos con un layout simple. Eso hace ventanas muy pesadas y que consumen mucho. Aprende a usar el GridBagLayout para hacer un solo panel con todo. La excepción a esto es que tengas pequeños JPanel reutilizables, como un editor de coordenadas geográficas que pida latitud, norte/sur, longitud, este/oeste, un panel que pida usuario y password, etc.
  • Todos los eventos de ratón y teclado se ejecutan en el mismo hilo que repinta las ventanas. Si en un actionPerformed(), keyPressed(), … tu código tarda mucho o pretendes que se pinte algo en una ventana, simplemente no lo hará hasta que tu código termine. Si tu código en un actionPerformed() va a tardar mucho o tiene que pintar cosas en la ventana, lanza un hilo aparte para hacer esa tarea y termina el actionPerformed() lo antes posible.

Groovy: un chat

Lunes, 23 de Abril de 2007

Tok es un programa de chat escrito en Groovyen dos horas por el autor de SH*TMORES, un blog sobre ingeniería de software. El autor (no he logrado encontrar cómo se llama) se quedó sin ADSL unos días, y decidió dedicar su tiempo libre a “aprender un poco más de Groovy”. Cogió su ejemplar de Groovy in Action, y en un par de horas tenía una sencilla aplicación de chat cliente-servidor.
El cliente tiene 63 líneas de código, el servidor solo 33, y la conectividad entre ambos se realiza mediante XML-RPC.

Vía Groovy.or.ges, el post original aquí

Cada día estoy más convencido de que tengo que aprender Groovy. Ya. Ahora. Solo de pensar la de cientos de líneas que me puedo ahorrar programando lo mismo que hago en Java pero en Groovy se me hace la boca agua.

Tok client

 1 import groovy.swing.SwingBuilder
 2 import groovy.net.xmlrpc.XMLRPCServerProxy as Proxy
 3 import groovy.net.xmlrpc.XMLRPCServer as Server
 4
 5 import java.net.ServerSocket
 6 import java.awt.BorderLayout as BL
 7 import javax.swing.WindowConstants as WC
 8
 9
10 def swing = new SwingBuilder()
11 def content = new StringBuffer()
12 def remote = new Proxy('http://localhost:8081/')
13
14 newMsg = swing.action( name:'newMessage', closure: {
15     msg = remote.chat( swing.inputMsg.text )
16     //content << " ${msg}"
17     //swing.msgPane.text = content
18     swing.inputMsg.text = ""
19 } )
20
21 def frame = swing.frame( title:'Tok' ) {
22     // Menu bar
23     menuBar {
24         menu('File') {
25             menuItem 'Quit'
26         }
27         menu('Edit') {
28         }
29         menu('Help') {
30             menuItem 'About'
31         }
32     }
33
34     // Panel
35     panel( layout: new BL() ) {
36         scrollPane( constraints: BL.CENTER ) {
37             editorPane( id:'msgPane', editable:false, preferredSize:[60, 100],
38                         editorKit: new javax.swing.text.html.HTMLEditorKit() )
39         }
40         panel(constraints: BL.SOUTH) {
41             textField(id:'inputMsg', columns:20)
42             button( action:newMsg, 'Go' )
43         }
44     }
45 }
46 content << "Tok session started on ${new Date().toString()}"
47 swing.msgPane.text = content
48
49 // Perform client registration to the chat network
50 int ticket = remote.register()
51 println "TICKET: ${ticket}"
52 def server = new Server()
53 server.startServer( new ServerSocket(ticket) )
54
55 server.update = { msg ->
56     print msg
57     content << "${msg}"
58     swing.msgPane.text = content
59 }
60
61 frame.pack()
62 frame.setDefaultCloseOperation( WC.EXIT_ON_CLOSE )
63 frame.show()

Tok Server

 1 import groovy.net.xmlrpc.XMLRPCServer as Server
 2 import groovy.net.xmlrpc.XMLRPCServerProxy as Proxy
 3 import java.net.ServerSocket
 4
 5
 6 def ticket = 9991
 7 def clients = []
 8
 9 def server = new Server()
10
11 server.chat = { msg ->
12     println msg
13         clients.each { client ->
14             //client.update( msg )
15             updateClient(client, msg)
16     }
17 }
18
19 def updateClient(client, msg) {
20     try {
21         client.update( msg )
22     } catch (Exception ex) {
23         println ex
24     }
25 }
26
27 server.register = {
28     clients << new Proxy("http://localhost:${ticket}")
29    return ticket++
30 }
31
32 def socket = new ServerSocket(8081)
33 server.startServer(socket)

Sobrecarga de métodos y casting de null

Martes, 17 de Abril de 2007

Supongamos que hemos sobrecargado un método en Java de la siguiente manera

public static void metodoSobrecargado(List miscosas) {
}

public static void metodoSobrecargado(String[] miscosas) {
}

Tenemos dos métodos llamados “metodoSobrecargado”, cada uno de ellos acepta un tipo de parámetro distinto e incompatible entre sí.

Ahora supongamos que necesitamos llamar a uno de ellos, pero pasándole null como parámetro. Podríamos intentar esto:

public static void metodoSobrecargado(String[] args) {

    metodoSobrecargado( null );   // Falla!!

}

Pero falla estrepitosamente al compilar. Podemos optar por no sobrecargar el método, meterle algún parámetro más a uno de ellos con el fin de poder diferenciarlos o, simplemente, podemos hacer un casting a null para que se adapte al tipo esperado por el método que elijamos, así:

public static void metodoSobrecargado(String[] args) {

    metodoSobrecargado( (List)null );

    metodoSobrecargado( (String[])null );

}

Puede parecer elemental, pero seguro que más de uno no se lo sabía.

Servidor web en Bash

Martes, 17 de Abril de 2007

Un servidor http programado en bash (una shell de línea de comandos para Linux) en tan sólo 20 líneas.

#!/bin/bash
# web.sh -- http://localhost:9000/hello?world

RESP=/tmp/webresp
[ -p $RESP ] || mkfifo $RESP

while true ; do
( cat $RESP ) | nc -l -p 9000 | (
REQ=`while read L && [ " " "<" "$L" ] ; do echo "$L" ; done`
echo "[`date '+%Y-%m-%d %H:%M:%S'`] $REQ" | head -1
cat >$RESP <<EOF
HTTP/1.0 200 OK
Cache-Control: private
Content-Type: text/plain
Server: bash/2.0
Connection: Close
Content-Length: ${#REQ}

$REQ
EOF
)
done

Visto en anieto2k, código original en http://paulbuchheit.blogspot.com/2007/04/webserver-in-bash.html

Serializando objetos con XStream

Lunes, 5 de Marzo de 2007

A veces puede ser necesario hacer que un simple bean u objeto sea persistente. Es decir, escribir a disco todos los atributos de un objeto dado para volver a leerlo en otro momento y recuperarlo. El sistema clásico para hacer esto es utilizando la interfaz java.io.Serializable y utilizando un ObjectOutputStream para guardarlo y un ObjectInputStream para recuperarlo.

public class MiObjeto implements java.io.Serializable {
	String nombre = "Mola";
	Collection datos = new ArrayList();
}

Creamos un par de métodos estáticos en una clase externa:

public class IOTools {
	public static void serialize(Object o, OutputStream os)
			throws IOException {
	    ObjectOutputStream oos = null;
	    try {
	        oos = new ObjectOutputStream(os);
	        oos.writeObject(o);
	    } finally {
	        if (oos != null) oos.close();
	    }
	}

	public static Object deserialize(InputStream os)
			throws IOException, ClassNotFoundException {
	    ObjectInputStream oos = null;
	    try {
	        oos = new ObjectInputStream(os);
	        Object o = oos.readObject();
	        return o;
	    } finally {
	        if (oos != null) oos.close();
	    }
	}
}

Y ahora podemos guardar y recuperar nuestros objetos de una manera muy simple:

	// Grabar
	MiObjeto o = new MiObjeto();
	IOTools.serialize(o, new FileOutputStream("objeto.dat");

	// Recuperar
	MiObjeto o = (MiObjeto)IOTools.deserialize(new FileInputStream("objeto.dat");

Sin embargo, este sistema tiene algunas complicaciones que para mi gusto, lo hacen muy poco interesante:

  1. El objeto serializado se guarda en un formato binario, por lo que es ilegible y no se puede editar externamente.
  2. Si se cambia el código de la clase serializada y se recompila la misma, es posible que no se pueda recuperar lo grabado.

¿Y cuál es la solución para todo esto? XStream. Tras mucho (bueno, no tanto) buscar, he dado con la solución definitiva:

  • Persistencia basada en XML, o sea texto plano: legible y editable.
  • No hace falta modificar tus objetos a serializar (ni siquiera tienen que implementar java.io.Serializable)
  • No requiere mapeos (como otros motores de persitencia basado en XML, como Castor)
  • Si recompilas tu objeto a escribir, o incluso si modificas sus atributos, el Xml grabado seguirá siendo válido (aunque puedes perder información si cambias el nombre de un atributo, siempre puedes editar el Xml!)
  • Controla referencias circulares. Es decir, es posible serializar un objeto con un atributo que es una referencia a si mismo.
  • Es fácil, sencillo, extensible, configurable, muy rápido y MOLA

¿Y como se usa? Pues así:

    XStream x = new XStream();

    // Grabar
    MiObjeto o = new MiObjeto();
    x.toXML(o, new FileOutputStream("objeto.xml"));

    // Recuperar
    MiObjeto o = (MiObjeto) x.fromXML(new FileInputStream("objeto.xml"));

Más sencillo, imposible. Además, podemos crear nuestras propias implementaciones de los conversores de objeto/xml y xml/objeto para ciertas clases, podemos persistir colecciones de objetos en el que se escribe un Xml en disco por cada objeto, podemos hacer alias de clases y cambiar etiquetas enteras por atributos para mejorar la legibilidad.
En resumidas cuentas, una herramienta muy recomendable.
Más información en: http://xstream.codehaus.org

UT8 cheat sheet

Martes, 20 de Febrero de 2007

O como hacer que tus aplicaciones web Java sean internacionales con UTF-8 sin morir en el intento.

  1. Páginas html (y jsps). Es necesario indicarle al navegador en que codificación van nuestras páginas. Para esto utilizaremos la etiqueta meta:
    <html>
        <head>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        </head>
    </html>
    
  2. Ficheros xml. Estos ficheros no soportan la etiqueta meta, así que es necesario la siguiente etiqueta especial que debe ir en la primera línea del fichero.
    <?xml version="1.0" encoding="UTF-8"?>
    
  3. Páginas jsps. Podemos utilizar diferentes maneras de indicar que el contenido de las mismas es utf8. Con la directiva @page pageEncoding le estamos indicando al compilador en que codificación se está guardando la página jsp en disco. Es importante que esta codificación sea la misma que hemos establecido en nuestro entorno de desarrollo.
    <%@page pageEncoding="UTF-8"%>
    

    Luego podemos añadir la directiva page contentType para que al servir la página se envíe la cabecera http contentType que le indica al navegador en que codificación va el contenido a enviar y así cambiar el juego de caracteres. Esta cabecera http y la etiqueta html meta que se ha descrito en el punto anterior son equivalentes, pero no está de más enviar las dos (por si acaso)

    <%@page contentType="text/html;charset=UTF-8"%>
    
  4. Leer parámetros desde servlets y jsps. Dado que ya le hemos indicado al navegador que el contenido de las páginas (jsps o html) están en utf8, el navegador enviará los parámetros de los formularios codificados en utf 8 también. Por esta razón, tenemos que establecer en nuestro Serlvet (o jsp) esta codificación antes de leer cualquier parámetro enviado desde estas páginas para que puede ser interpretado correctamente. Una vez establecida la codificación, ya no podemos cambiarla durante esta petición, por lo tanto, es lo primero que tenemos que hacer nada más empezar a trabajar.
    request.setCharacterEncoding("UTF-8");
    String parametro = request.getParameter("parametro");
    // Esta parametro ya esta codificado correctamente y podemos leerlo
    
  5. Escribir datos desde servlets. Primero es necesario establecer la codificación de los caraceteres a escribir. Para escribir la salida, el objeto OutputStream de la petición actual nos permite enviar bytes directamente sin ningún tipo de codificación, lo cual lo convierte en el candidato ideal para enviar información en bruto como imágenes u otros ficheros. Pero si lo que queremos es utilizar nuestra codificación utf8 para imprimir texto, debemos utilizar el objeto Writer del HttpServletResponse de nuestra petición.
    response.setCharacterEncoding("UTF-8");
    response.getWriter().print("Codificando con utf8: ñáéíóú");
    

    Si por alguna razón necesitáramos escribir la información a través del OutStream de la petición actual, debemos convertira la cadena de texto a UTF-8 antes:

    String cadenaUtf = new String(cadenaDistinta.getBytes("UTF-8"));
    response.getOutputStream().print(cadenaUtf);
    
  6. Si usas base de datos, crear tu instancia y las tablas en UTF-8.
    Por ejemplo, en MySql se hace así:

    CREATE DATABASE nombreBaseDeDatos CHARACTER SET utf8;
    
    CREATE TABLE nombreTabla (
    	... campos ...
    ) CHARSET=utf8;
    

    De todas formas, consulta la documentación de MySql (o de la base de datos que utilices) para asegurarte de que estás creando todo en utf8.

  7. Escribir ficheros de texto (o sobre cualquier fuente de datos) codificando la salida en un juego de caracteres concreto. En este caso, necesitaremos utilizar un Writer el cual nos permite hacer esta conversión. Para este caso, crearemos un OutputStreamWriter a partir de un FileOutputStream, aunque se podría haber utilizado cualquier otra clase que heredase de OutputStream. El PrintWriter es solo una capa más que nos permite escribir cualquier tipo de objeto o dato sin tener que convertirlo a texto, lo cual es más comodo que si utilizamos el Writer directamente.

    Este ejemplo escribe un fichero información codificada en ISO-8859-15

    OutputStream os = new FileOutputStream(new File("prueba.txt"));
    Printer p = new PrintWriter(new OutputStreamWriter(os, "ISO-8859-15"));
    p.print("Codificando con utf8: ñáéíóú");
    p.flush();
    p.close();
    
  8. Leer ficheros de texto (o cualquier fuente de datos) codificados en otro juego de caracteres y volcarlos desde un Servlet. Siempre que necesitemos leer algo en una codificación concreta, necesitaremos un Reader. Para este caso, crearemos un InputStreamReader a partir de un FileInputStream, aunque se podría haber utilizado cualquier otra clase que heredase de InputStream. El BufferedReader es solo una capa más que nos permitirá acelerar la lectura utiliznado un buffer intermedio.

    Este ejemplo lee el fichero del ejemplo anterior codificado en ISO-8859-15 y lo escribe desde un Serlvet codificado en UTF-8

    response.setCharacterEncoding("UTF-8");
    
    InputStream is = new FileInputStream(new File("prueba.txt"));
    
    Reader br = new BufferedReader(new InputStreamReader(is, "ISO-8859-15"));
    String line = br.readLine();
    while (line != null) {
    	response.getWriter().print(line);
        line = br.readLine();
    }
    br.close();
    

Y con esto creo que se cubren todos los puntos clave que una aplicación realizada en JSP/Servlets debe cumplir para que sea utf8: desde la codificación del navegador en los html, el envío y recepción de parámetros y la escritura/lectura de los mismos, ya sea en base de datos o en ficheros de texto con otra codificación más cómoda para trabajar localmente como pudiera sera ISO-8859-15.

Descargate la versión imprimible: ut8-cheat-sheet.htm

Ofuscando emails desde Java

Domingo, 11 de Febrero de 2007

Para evitar el spam, hay muchas maneras de ocultar el email en una página html. Una de las más efectiva podría ser crear una imagen con la dirección como tengo puesto en mi página de contacto: . Puedes crear esta imagen con el photoshop o desde alguna utilidad externa como esta.

Sin embargo, puede que estemos desarrollando una aplicación en Java y tengamos un listado de usuarios público con sus emails. Crear una imagen para cada uno de ellos podría ser muy laborioso, y escribir el email sin ofuscar u ocultar podría ser peligroso.

Obviando los trucos de poner los emails con el formato “nombre ARROBA dominio PUNTO es” o “email@[QUITAESTO]dominio.es” podemos escribir cada carácter ASCII de nuestro email uno por uno en hexadecimal o decimal. Para esto, utilizaremos las entidades html con este formato:

  • &#D; siendo D el número del código ASCII en decimal
  • &#xH; siendo H el número del código ASCII en hexadecimal

Por ejemplo, para pintar la letra a, cuyo código ASCII es 97 en decimal y 61 en hexadecimal podemos utilizar &#97; o &#x61;
(Una tabla completa con todos los caracteres ASCII aqui)

Bueno, ahora lo que queremos es escribir nuestros emails codificados en decimal y hexadecimal. Para eso utilizaremos esta clase Java:

public class HtmlCoder {
   public static final Random r = new Random(System.currentTimeMillis());
   public static String randomHtmlEncode(String data) {
        if (data == null || data.length() == 0) return data;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < data.length(); i++) {
            switch (r.nextInt(3)) {
            case 0 : // En decimal
                sb.append("&#").append((int)data.charAt(i)).append(";");
                break;
            case 1 : // En hexadecimal
                sb.append("&#x").append(Integer.toHexString(data.charAt(i))).append(";");
                break;
            case 2 : // Tal cual
                sb.append(data.charAt(i));
            }
        }
        return sb.toString();
    }
}

Esta clase crea un texto codificando cada carácter en su equivalente ascii en decimal, hexadecimal o sin codificar de manera aleatoria. De esta manera, un email se quedaría así: &#x73;&#x6f;&#x79;&#x70;ic&#x61;p&#105;&#99;a&#x40;&#x79;&#97;&#x68;o&#x6f;.&#x65;&#115;

Si los programadores construyeran aviones

Jueves, 21 de Diciembre de 2006

We build your digital business,
even while you’re up and running.




Ver video en Youtube

Algoritmo de ajedrez en solo 5kb

Lunes, 11 de Diciembre de 2006

Vía anieto2k me entero de un ajedrez en Javascript de sólo 5kb, escrito por Douglas Bagnall en 2002 en una competición. El código está un poco ofuscado eso sí, pero te hace frente una partida entera. Se basa en la búsqueda recursiva en el árbol de movimientos posibles de cada ficha, dando una puntuación a cada movimiento y eligiendo el mejor. Se permite algunas licencias, como seguir jugando después de un jaque, pero para lo que ocupa es más que suficiente.
Vamos, échate una partida. No te va a ganar un ordenador con sólo 5kb de ventaja, ¿o sí?
Ajedrez en Javascript en sólo 5kb

Be void* my friend

Viernes, 17 de Noviembre de 2006

Mola, mola, mola!!

be-water-bruce-lee.jpg Don’t get set into one form, adapt it and build your own, and let it grow, be like void*. Empty your mind, be formless, shapeless - like void*. If you put an int into a void*, it becomes the int. You put float into a void* it becomes the float. You put in a char it becomes the char. Now, void* can flow or it can overflow.

Be void*, my friend.

Por si acaso no sabes de qué va, puedes consultar lo que significa void* en la Wikipedia. (Vía Halon disparado, post original en El blog de Lluis Franco)