A veces para afrontar un problema no tenemos mas remedio que cambiar el punto de vista. Como muestra, un caso real:
Queremos generar un fichero de salida tipo csv, en texto plano, mediante un proceso batch que utiliza una clase java. El encoding de dicho fichero debe ser ISO 8859-15, similar al ISO-8859-1 (también conocido como LATIN1) pero incluyendo, entre otros, el símbolo del euro.
En la fase de pruebas comprobamos que el fichero se genera incorrectamente. Al abrirlo en Windows con Notepad este muestra texto chino, codificado en UTF-8. Lo mismo ocurre al abrirlo con gedit en GNU/Linux.
Inicialmente las pesquisas para encontrar el origen del problema se centran en el encoding del texto: se comprueba la codificación pasada al PrintWriter encargado de escribir el fichero, la codificación de la base de datos de la que se obtienen los datos a escribir. En definitiva se chequea todo el código relacionado sin encontrar ningún error que pueda explicar la codificación incorrecta. Por ello decidimos pasar de examinar el código fuente a examinar el resultado de su ejecución, esto es, el propio csv. Utilizando un editor hexadecimal revisamos los bytes que lo componen en busca de secuencias erróneas, comprobando en particular la validez de la codificación los caracteres no ASCII. No encontramos nada raro.
El siguiente paso es comprobar posibles caracteres de control, y aquí si obtenemos resultados: ocurrencias de bytes a 0, que se corresponden con el carácter NULL y que no deberían aparecer en nuestro fichero. En efecto, sustituyendo cada byte 0 por otro byte correspondiente a un carácter ASCII (p.e. 20h, espacio) conseguimos que el csv se abra correctamente.
Ahora sabemos lo que causa el problema, pero no donde se origina en el código fuente. Nuestro principal sospechoso es algún String inicializado a null, sin embargo la documentación del API del PrintWriter dice que el método print(String s)
“Prints a string. If the argument is null then the string “null” is printed”
Por lo cual tenemos que descartar las cadenas como origen del problema. Nuestro siguiente candidato es un char sin inicializar, y efectivamente es el caso.
Si intentamos escribir un char sin inicializar declarado localmente al método que realiza la escritura recibimos un error en tiempo de compilación: variable carácter might not have been initialized. Pero en nuestro caso el char en cuestión está definido en otra clase y es accedido mediante un getter, por lo que el compilador no puede realizar la comprobación de inicialización y el .class se genera sin problemas.
La solución es sencilla una vez identificado el culpable y el texto se genera correctamente en ISO 8859-15.
Por curiosidad seguimos analizando un poco mas el tema y vemos que, al realizar distintas pruebas que generan ficheros con distintos contenidos se producen dos casos:
- El caso detectado inicialmente (y el mas frecuente): el editor Notepad de Windows lo interpreta como chino, al igual que el editor Gedit en Ubuntu. Mi teoría es que realmente se genera un fichero UTF-8 bien formado, pero el editor no puede identificar unívocamente el charset y utiliza un charset del chino como fallback.
- Notepad muestra correctamente el fichero, pintando el byte a 0 como un espacio en blanco (lo interpreta como carácter NULL, \u0000 ). Sin embargo Gedit no reconoce ningún encoding y no abre el fichero, considerándolo binario.
En situaciones como esta posiblemente es mas rápido analizar a bajo nivel el objeto generado que revisar el código que lo genera, que puede ser (como en este caso) bastante complejo.

