Codificación de carácteres y Java
Hace unos días estuve peleándome(otra vez) con la codificación de carácteres en Java, se puede convertir en algo divertido el no saber exáctamente con qué codificaciones trabajas en cada momento cuando tienes varios orígenes de datos y recursos.
La cuestión es que al estar utilizando Grails que por defecto en la parte web utilizará UTF-8 para generar el html(se puede modificar, pero mejor usar UTF-8 para la internacionalización de carácteres), había que tratar de poner todas las codificaciones de carácteres en común (bases de datos, ficheros de texto, ficheros javascript...).
Primero, tenemos que asegurarnos de que nuestro IDE/editor guarde nuestros ficheros con formato UTF-8, igual que nuestra base de datos guarde el contenido con esta codificación, en MySQL:
CREATE DATABASE mydatabase CHARACTER SET utf8;
CREATE TABLE mytable (
...
) ENGINE=InnoDB CHARSET=utf8;
En PostgreSQL:
CREATE DATABASE mydatabase WITH ENCODING 'UTF8';
...
Por otro lado nos encontramos que Java utiliza nativamente UTF-16 (ver el api de la clase Charset) "The native character encoding of the Java programming language is UTF-16", por lo que Groovy y Grails heredan esta codificación de carácteres.
Para seguir la uniformidad, la lectura/escritura de ficheros desde java la debemos hacer en UTF-8, la lectura la podemos hacer por medio de un InputStreamReader pasándole un FileInputStream y para la escritura por medio de un OutputStreamWriter con un FileOutputStream, y diciéndoles a ambos contructores con qué codificación, en nuestro caso UTF-8:
InputStreamReader isr = new InputStreamReader(myFileInputStream, "UTF-8")
...
OutputStreamWriter osw = new OutputStreamWriter(myFileOutputStream, "UTF-8")
Por último, y para rizar el rizo, nos encontramos que los ficheros Properties(y ResourceBundles) se cargan con codificación ISO-8859-1, por lo que en este caso nuestro editor debe escribir con esta codificación, y para leerlo programáticamente podemos hacer la transformación de la codificación a nivel de byte, esto sería:
Corregido por los comentarios de GreenEyed
ResourceBundle bundle = ResourceBundle.getBundle("com.danilat.foo")
String valueIso = bundle.getString(key)
String value = new String (valueIso.getBytes(), "ISO-8859-1");
Y en caso de que necesitemos forzar la conversión a UTF-8 de una cadena:
String valueUtf8= new String(string.getBytes("UTF-8"));
Por último, no debemos olvidarnos de añadir el tag meta content-type con el charset UTF-8 en el html, para que los navegadores sepan con qué codificación estamos mostrando nuestro contenido:
[meta http-equiv="content-type" content="text/html; charset=UTF-8"]
Como bonus, para Grails no es necesario ya que la codificación de carácteres es configurable en el Config.groovy, pero si quisieramos devolver contenido UTF-8 desde un Servlet o con un framework donde no fuera posible configurarlo, simplemente debemos acceder al objeto response y modificar la codificación de la respuesta :
response.setCharacterEncoding("UTF-8");
Si alguien quiere más explicaciones sobre codificaciones de carácteres y en particular sobre unicode, hay un artículo que para mi es de referencia de Joel Spolsky sobre Unicode, en inglés. También es interesante un post de Joaquín Cuenca en Programa con Google sobre el tema.
Por cierto, mucho cuidado con el Byte Order Mark (BOM) en ficheros UTF-8, que nos llevará a situaciones extrañas ya que Java lo reconoce como un espacio en blanco al principio de un fichero.