Minientrada

Breve introducción a las expresiones lambda.

Expresiones lambda

Las expresiones lambda no son complejas de usar y entre otras ventajas permiten pasar comportamiento como valor, algo muy utilizado en Javascript, y abreviar líneas de código sin perder legibilidad en el código. Es por esto que Java las adoptó definitivamente en la JavaSE 8. Pero, para que las expresiones lambda sean verdaderamente útiles, hay que llegar a entenderlas. Hasta que eso ocurre pueden llegar a ser bastante confusas.

Si nos atenemos a la definición oficial de expresión lambda diríamos que son funciones anónimas que implementan una interfaz funcional, que así dicho suena muy técnico, pero si te lo dicen así sin más, puede dejarte algo frío. A mi es lo que me pasó, así que me puse manos a la obra para intentar aterrizar el concepto y desgranar la definición teórica haciéndola comprensible para mortales como yo. De ese germen surgió esta entrada.

Lo que me quedó claro de la definición es qué, para entender las expresiones lambda hay que entender qué es una función anónima y que es una interfaz funcional.

Función anónima

Las funciones anónimas permiten crear instancias de un objeto que implementa un interfaz en particular sin que para ello sea necesario desarrollar la clase que implementa esa interfaz explícitamente.

¡Toma ya!

Esto se ve claro con un ejemplo. Imaginemos una interfaz que ofrece métodos para una calculadora:

package com.tecnicomio.pruebas.funcionesanonimas;

public interface ICalculadora {

    Integer suma (Integer operando1, Integer operando2);

}

De manera tradicional tendríamos una clase que implementara la interfaz:

package com.tecnicomio.pruebas.funcionesanonimas;

public class Calculadora implements ICalculadora {

    @Override
    public Integer suma(Integer operando1, Integer operando2) {
        return operando1 + operando2;
    }
}

Posiblemente, utilizaríamos esta clase en otra parte del código de la siguiente manera:

[...]
       ICalculadora calculadora = new Calculadora();
       Integer operando1 = 10;
       Integer operando2 = 22;
       Integer resultado = calculadora.suma(operando1, operando2);
       System.out.println(operando1 + "+" + operando2 + "=" + resultado);
[...]

Ahora haré una aproximación con funciones anónimas. La interfaz se mantiene, pero la clase desaparece.

El código resultado sería:

[...]

       Integer operando1 = 10;
       Integer operando2 = 22;

       System.out.println(operando1 + "+" + operando2 + "=" +
                new ICalculadora() {
                    @Override
                    public Integer suma(Integer operando1, Integer operando2) {
                        return operando1 + operando2;
                    }
                }.suma(operando1, operando2));
[...]

Repasando qué hemos conseguido con la función anónima, llegamos a las siguientes conclusiones:

  • Nos hemos ahorrado la definición y creación de la clase, que en el caso de clases que se usan en un único sitio, puede ser un gran ahorro. Así evitamos contaminar el proyecto con esas clases que no se reutilizan en otras partes.
  • Cuando la implementación es corta, como en el caso de este ejemplo, en el que el código se encuentre directamente en el lugar donde se usa, puede hacer que éste sea más entendible (aunque tal vez menos legible).
  • Permite seguir manejando correctamente las variables locales y miembros de la clase sin tener que definir un constructor para poder recibirlas y usarlas.

En este ejemplo, es probable que el ahorro de crear la clase, versus la disminución de legibilidad, haga que no merezca suficientemente la pena, pero pongamos un ejemplo donde verdaderamente las funciones anónimas dan la talla. Imaginemos una ventana con 15 botones y sus respectivos 15 event listeners cada vez que el usuario hace click en ellos. En este caso, habría que crear 15 clases donde cada una implementa el interfaz ActionListener. Clases que solo se usan en el punto del código donde se implementa el formulario. En este caso, usar funciones anónimas es mucho más rentable.

Interfaz Funcional

Un interfaz funcional es aquel interfaz que tiene únicamente un método y este método es abstracto, es decir un método sin implementar. Las interfaces funcionales fueron agregadas a partir de la versión JavaSE 8 y vienen de la mano de las expresiones lambda.

A continuación dos ejemplos de interfaz funcional. El primero es un ejemplo al uso, con un único método abstracto.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalSimple {
  public String saludo(String nombre);
}

El segundo es un ejemplo un poco más rebuscado pero igualmente válido; un único método abstracto y varios métodos default.

package com.tecnicomio.interfazfuncional;

public interface IInterfazFuncionalRebuscada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

Para asegurarnos que la interfaz cumple con las reglas de las interfaces funcionales podríamos anotar la interfaz con @FunctionalInterface. En este caso si introdujéramos más de un método abstracto (sin implementación) el compilador nos daría el error: «Multiple non overriding abstract methods found in interface com.tecnicomio.pruebas.interfazfuncional.InterfazFuncionalAnotada». En caso de no introducir ningún método abstracto, nos daría el error: «No Target method found».

package com.tecnicomio.interfazfuncional;

@FunctionalInterface
public interface IInterfazFuncionalAnotada {
  public String saludo(String nombre);

  public default String holaMundo() {
    Return "Hola mundo.";
  }
}

En el ejemplo de la calculadora, la interfaz ICalculadora es un claro ejemplo de Interfaz Funcional, sin anotar.

Expresiones lambda

Y ahora que ya han quedado un poco más claros los conceptos «funciones anónimas» e «interfaces funcionales» volvamos a la definición de expresión lambda: las expresiones lambda son funciones anónimas que implementan una interfaz funcional.

Por tanto, podríamos decir que son una evolución de las funciones anónimas que pretenden simplificar aún más el código, pero que para conseguir esta simplificación, obligan a que la funcionalidad de la expresión lambda implemente una interfaz funcional.

Sintaxis de las expresiones lambda

La sintaxis de las expresiones lambda es:

	
(Parametros) -> { cuerpo expresión lambda }

Teniendo en cuenta qué:

  • El operador lambda (->) separa la declaración de parámetros del cuerpo de la función.
  • Parámetros
    • Cuando se tiene un solo parámetro pueden omitirse los paréntesis.
    • Cuando no se tienen parámetros, o cuando se tienen dos o más, sí es necesario su uso.
  • Cuerpo de la expresión lambda
    • Cuando el cuerpo de la expresión lambda tiene una única línea pueden omitirse las llaves y no se necesita especificar la cláusula return en el caso de que se devuelva valor.

Ejemplos de expresiones lambda pueden ser:

  • Expresión lambda con un único parámetro y una única línea: num -> num+10
  • Expresión lambda sin parámetros y una única línea: () -> System.out.println(«Hola mundo»)
  • Expresión lambda con dos parámetros y una única línea: (int operando1, int operando2) -> operando1 * operando2
  • Expresión lambda con múltiples parámetros y varias líneas de función: (String nombre) -> {String retorno=»Hola «; retorno=retorno.concat(nombre); return retorno;}

La calculadora con expresiones lambda

Y como lo mejor para entender algo es verlo con un ejemplo, apliquemos las expresiones lambda al ejemplo de la calculadora que podría quedar así:

        ICalculadora calculadora = (Integer operando1, Integer operando2)->(operando1+operando2);

        Integer operando1 = 10;
        Integer operando2 = 22;

        System.out.println(operando1 + "+" + operando2 + "=" + calculadora.suma(operando1,operando2));

O si queremos ahorrar aún más líneas de código sin perder legibilidad podría quedar de esta otra forma:

    System.out.println("En una línea:10+22=" + ((ICalculadora)(Integer operando1, Integer operando2)->(operando1+operando2)).suma(10,22));

Conclusiones

Como se puede ver en el ejemplo de la calculadora utilizar expresiones lambda tienen claros y algunos obscuros.

Para mí los claros son:

  • Al hacer uso de funciones anónimas sin necesidad de crear clases anónimas se crea código más claro y conciso.
  • Acercan Java a la programación funcional muy utilizado en lenguajes de script, como Javascript, donde las funciones juegan un papel protagonista. Esto permite poder pasar funciones como valores de variables, valores de retorno o parámetros de otras funciones, es decir, gracias a las expresiones lambda se puede pasar comportamiento como valor.
  • Al usarlas en combinación con la API Stream se pueden realizar operaciones de tipo filtro/mapeo sobre colecciones de datos de forma secuencial o paralela siendo la implementación transparente al desarrollador.
  • Logran un código más compacto y fácil de leer.
  • Reducen la escritura de código.

El oscuro para mí es claro:

  • Hay que entenderlas para sacarles el máximo partido. Y entenderlas es cambiar la manera de pensar del javero de toda la vida.

Links

Minientrada

Tomcat 8.5 en Eclipse Neon 4.6.0 (Build id: 20160613-1800)

No existe Runtime Enviroment para Tomcat 8.5 en Eclipse Neon.

Me descargué el otro día la nueva versión de Eclipse para comenzar mis nuevos proyectos basados en JEE 6 con un entorno de desarrollo puesto al día. Adicionalmente descargué también la última versión de Apache Tomcat 8.5 para probar los trabajos.

Mi sopresa surgió cuando al enlazar la versión de Eclipse con la Server Runtime de Apache Tomcat 8.5, encuentro que puedo seleccionar la versión 8.0 y la 9.0, pero no la susodicha.

2016-08-25_10-57-12

Al seleccionar cualquiera de las otras dos 8.0 o 9.0 obtengo el error «The Apache Tomcat installation at this directory is version 8.5.4. A Tomcat 8.0 (9.0 si selecciono la 9) installation is expected«.

Buscando en la base de datos Bugzilla de Eclipse encuentro el bug 494936. En el se comenta  de forma resumida que Eclipse Neon todavía no está preparado para Apache Tomcat 8.5 y que está previsto para futuras versiones.

Una solución temporal.

Para salir del paso, en el propio caso abierto de bugzilla, Levan Kekelidze proporciona una nueva versión del plugin de tomcat que permite añadir Tomcat 8.5 como si fuera la versión 9.0 del server runtime.

La versión de este plugin está publicado por Levan como attachment y se puede descargar directamente desde este enlace.

Se reemplaza el fichero org.eclipse.jst.server.tomcat.core_1.1.800.v201602282129.jar que se encuentra en <ECLIPSE_NEON_INSTALLATION_DIR>\plugins por el fichero descargado en el enlace previo.

Et voila!!! Ya podemos agregar una instancia de Apache Tomcat 8.5 como si fuera de tipo Apache Tomcat 9.0.

2016-08-25_11-13-11

Mientras sale el parche que permita agregar de manera nativa la Server Runtime Enviroment para Apache Tomcat 8.5 se puede continuar trabajando de manera temporal con esta solución. Con esta «solución», todo funciona correctamente, se puede arrancar, parar, depurar y desplegar aplicaciones desde Eclipse al servidor.

Espero que esta entrada os sea de utilidad.

 

Extraer un XSD a partir de un XML existente

A veces me he encontrado en la tesitura de tener un XML maravilloso con toda la información que necesito y querer explotar su información mediante Java.

Si utilizamos JAXB, el estándar de Java para el tratamiento XML-JAVA, veremos que el compilador xjc, incluido en la JDK (a partir de la versión 1.6.0_3), necesita un XSD para generar los objetos java correspondientes.

Por tanto, el primer paso, y el motivo de esta entrada, es tratar las diferentes maneras que hay de convertir el XML en un XSD.

Voy a utilizar para la generación del XSD un XML de ejemplo sencillo, pero suficiente, para ver las diferencias entre las herramientas presentadas.

<?xml version="1.0" encoding="UTF-8"?>
<servers>
	<server id="svnserver.com">
		<ip>192.168.1.1</ip>
		<osuser>souser</osuser>
		<ospass>sopass</ospass>
		<repositories>
			<repository name="a53">
				<url>http://svnserver.com/svn/a53</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
			<repository name="i07">
				<url>http://svnserver.com/svn/i07</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
		</repositories>
	</server>
	<server id="svnserver2.com">
		<ip>192.168.1.2</ip>
		<osuser>souser</osuser>
		<ospass>sopass</ospass>
		<repositories>
			<repository name="a53_backup">
				<url>http://svnserver.com/svn/a53_backup</url>
				<svnuser>user</svnuser>
				<svnpass>pass</svnpass>
			</repository>
		</repositories>
	</server>
</servers>

Trang.jar (http://www.thaiopensource.com/relaxng/trang.html)

Este programa no se actualiza desde 2008, pero tampoco es preocupante porque tampoco ha cambiado la especificación W3C de los XML Schemas. Se puede descargar directamente desde su página web y es muy sencillo de utilizar.

usage: java -jar trang.jar 
		[-I rng|rnc|dtd|xml] 
		[-O rng|rnc|dtd|xsd] 
		[-i input-param] 
		[-o output-param] 
		inputFileOrUri ... outputFile

Partiendo del XML de ejemplo propuesto, la respuesta de trang ha sido la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="servers">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="server"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="server">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="ip"/>
        <xs:element ref="osuser"/>
        <xs:element ref="ospass"/>
        <xs:element ref="repositories"/>
      </xs:sequence>
      <xs:attribute name="id" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="ip" type="xs:NMTOKEN"/>
  <xs:element name="osuser" type="xs:NCName"/>
  <xs:element name="ospass" type="xs:NCName"/>
  <xs:element name="repositories">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="repository"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="repository">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="url"/>
        <xs:element ref="svnuser"/>
        <xs:element ref="svnpass"/>
      </xs:sequence>
      <xs:attribute name="name" use="required" type="xs:NCName"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="url" type="xs:anyURI"/>
  <xs:element name="svnuser" type="xs:NCName"/>
  <xs:element name="svnpass" type="xs:NCName"/>
</xs:schema>

El XSD generado es un XSD básico, sencillo y útil. Lo único que no termina de convencerme es la tipificación xs:NCName (Non Colonized Name) que le ha dado a las cadenas de texto. No es una tipificación incorrecta, pero limita el uso de qualified names (nombres con namespace) en el contenido del texto.

inst2xsd (http://xmlbeans.apache.org/index.html)

La herramienta inst2xsd viene incluida en el software XMLBEANS de Apache Foundation que se puede descargar directamente desde su página web. Esta herramienta es más completa que Trang, aunque sin ser difícil también es más compleja de utilizar.

Usage: inst2xsd [opts] [instance.xml]*
Options include:
    -design [rd|ss|vb] - XMLSchema design type
             rd  - Russian Doll Design - local elements and local types
             ss  - Salami Slice Design - global elements and local types
             vb  - Venetian Blind Design (default) - local elements and global complex types
    -simple-content-types [smart|string] - Simple content types detection (leaftext). Smart is the default
    -enumerations [never|NUMBER] - Use enumerations. Default value is 10.
    -outDir [dir] - Directory for output files. Default is '.'
    -outPrefix [file_name_prefix] - Prefix for output file names. Default is 'schema'
    -validate - Validates input instances agaist generated schemas.
    -verbose - print more informational messages
    -license - print license information
    -help - help imformation

Para generar el siguiente XML he utilizado la opción -enumerations never para evitar que los valores que se incluyen en el ejemplo se convierten en enumeraciones en el XSD final.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="servers" type="serversType"/>
  <xs:complexType name="repositoryType">
    <xs:sequence>
      <xs:element type="xs:anyURI" name="url"/>
      <xs:element type="xs:string" name="svnuser"/>
      <xs:element type="xs:string" name="svnpass"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="name" use="optional"/>
  </xs:complexType>
  <xs:complexType name="repositoriesType">
    <xs:sequence>
      <xs:element type="repositoryType" name="repository" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="serverType">
    <xs:sequence>
      <xs:element type="xs:string" name="ip"/>
      <xs:element type="xs:string" name="osuser"/>
      <xs:element type="xs:string" name="ospass"/>
      <xs:element type="repositoriesType" name="repositories"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="id" use="optional"/>
  </xs:complexType>
  <xs:complexType name="serversType">
    <xs:sequence>
      <xs:element type="serverType" name="server" maxOccurs="unbounded" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

El XSD generado con esta herramienta es aún más simple que el generado con Trang. Los objetos que contienen texto los ha tipificado como xs:string que puede contener cualquier tipo de texto.

Conclusión

Existen más herramientas en el mercado para obtener ficheros XML Schema a partir de ficheros XML. Algunas son de pago, otras son más complejas, otras tienen interfaz gráfico, pero las dos herramientas descritas en esta entrada son suficientes para obtener XSD de calidad de forma fácil y gratuita.

Si bien me ha gustado más la generación de la herramienta inst2xsd por la generación menos restrictiva de las cadenas de texto, ambas herramientas han generado XSD aptos para utilizar con JAXB y poder generar las clases Java necesarias para tratar la información del XML.

Links

Xml Schema W3C
NCName

Internacionalizando aplicaciones con Java. Visión básica.

La internacionalización, también conocida como i18n (es curioso, pero en inglés hay 18 letras entre la letra «i» y la letra «n» de la palabra internacionalization), es la habilidad de una aplicación para adaptarse a distintos idiomas sin tener que realizar cambios en el código fuente.

Las características principales que debe cumplir una aplicación internacionalizada son:

  • con un mismo ejecutable de la aplicación se debe poder ejecutar la aplicación en distintos idioma simplemente cambiando la información de localización regional del usuario.
  • los textos susceptibles de cambiar de idioma no pueden incluirse directamente en el código. A cambio en el código aparecerá una variable que servirá para identificar el verdadero texto a mostrar.
  • soportar nuevos idiomas no debe requerir la re-compilación de la aplicación.
  • la información que pueda depender de la configuración regional del usuario aparecerá con el formato adecuado. Por ejemplo, moneda, números, fechas, etcétera.
  • el diseño de la aplicación (desde un inicio) debe estar orientado a la internacionalización.

En un alto porcentaje de los proyectos en los que participo conviven Euskera y Castellano, por lo que es muy necesario tener claros los conceptos básicos de la internacionalización.

Mi intención en esta entrada es, a través de un ejemplo, explorar los conceptos más básicos de i18n, entendiendo las ventajas que proporciona y la manera en la que lo hace.

La aplicación de ejemplo.

La aplicación de ejemplo que voy a utilizar para ilustrar el uso de la internacionalización recoge un nombre por teclado y lo escribe en la consola. Es un ejemplo muy simple pero muy útil para mostrar el funcionamiento de i18n.

La aplicación en funcionamiento.

El código fuente de la aplicación, sin internacionalizar es una clase verdaderamente simple; BasicConsoleLogin.java. Consta de dos métodos, uno privado y otro publico, los dos estáticos. El método privado readInput() es el encargado de leer el nombre introducido por el usuario a través del teclado. El método publico main(String[] args) contiene el código funcional de la aplicación.

/**
 * <p>Recoge la entrada del usuario vía teclado. </p>
 * @return java.lang.String la entrada insertada por teclado.
 */
 private static String readInput()
 {
   BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));
   String s = new String("");
   try {
     s = bufferRead.readLine(); 
   } catch (IOException e) {
     s = getStackTrace(e);
   }
   return s;
 }

 public static void main(String[] args) {
    System.out.println("Introduce tú nombre:");
    String nombre= readInput();
    System.out.println("Tu nombre es " + nombre);
    System.out.println("Muchas gracias por darme uso");
 
 }

Paso 1:  creación de los bundles de internacionalización.

A través de la internacionalización, el texto de la aplicación que es susceptible de ser traducido a diferentes idiomas se organiza en ficheros de propiedades, uno por idioma.

Estos ficheros se conocen como ‘bundles’ y contienen múltiples filas que constan de una estructura clave=mensaje_traducido. La clave aparecerá en todos los bundles y el mensaje_traducido variará conteniendo la cadena de texto que corresponde con el idioma.

Para entender lo anterior lo mejor es explicarlo con los ‘bundles’ que voy a utilizar en la aplicación de ejemplo:

Bundle: BasicConsoleLoginMessages_es_ES.properties

LanguageSelection = El idioma elegido es:
InputMessage = Introduce tú nombre:
OutputMessage = Tu nombre es 
AcknowledgeMessage = Muchas gracias por darme uso.

Bundle: BasicConsoleLoginMessages_eu_ES.properties

LanguageSelection = Hizkuntza hau aukeratu duzu:
InputMessage = Idatzi zure izena:
OutputMessage = Zure izena ... da: 
AcknowledgeMessage = Eskerrik asko erabiltzeagatik!

Bundle: BasicConsoleLoginMessages_en_UK.properties

LanguageSelection = The Language chosen is:
InputMessage = Please, type your name:
OutputMessage = Your name is 
AcknowledgeMessage = Thanks for using me!

La aplicación de ejemplo está traducida a tres idiomas: castellano (es_ES), euskera (eu_ES) e inglés (en_UK).

Por cada idioma hay un ‘bundles’ que contienen pares de valores clave = traducción. La clave es igual en todos los ‘bundles’ independientemente del idioma, sin embargo, la traducción es propia de cada uno. Por ejemplo, el mensaje que se usa para animar al usuario a teclear su nombre se llama InputMessage. Este nombre será el que se utilice en la aplicación y en cada ‘bundle’ tendrá una traducción distinta.

Paso 2: Recoger el idioma en el que se ejecuta la aplicación.

Para establecer el idioma en el que el usuario quiere ver la aplicación, inicialmente se pregunta por el idioma. El usuario podrá elegir entre castellano, euskera e inglés, y con esa información la aplicación continuará ejecutándose en el idioma elegido.

La aplicación en euskera.

Normalmente, las aplicaciones suelen recoger la configuración de idioma de la configuración regional del sistema operativo o pueden incorporar un icono en pantalla para el cambio de idioma. Existen múltiples métodos.

En nuestra aplicación de ejemplo, el usuario teclea el idioma que desea. La información que teclea el usuario sirve para crear una instancia de la clase java.util.Locale que usaremos para acceder al ‘bundle’ correspondiente del idioma elegido. El método getLocale(String appIdioma) muestra esta manera de proceder.

private static Locale getLocale(String appIdioma)
 {
   Locale locale;
   if ("es_ES".equals(appIdioma)) {
     locale = new Locale("es", "ES");
   } else if ("eu_ES".equals(appIdioma)) {
     locale = new Locale("eu", "ES");
   } else if ("en_UK".equals(appIdioma)) {
     locale = new Locale("en", "UK");
   } else {
     locale = new Locale("eu", "ES");
   }
   return locale;
 }

Paso 3. Internacionalizar la aplicación

El último paso es adaptar la aplicación para ser internacionalizada. El código fuente internacionalizado quedaría así:

public static void main(String[] args) {
  System.out.println("Sartu aplikazioaren hizkuntza / Introduce el idioma de la aplicación / Tell us application's language (eu_ES|es_ES|en_UK): ");
 
  String appIdioma = readInput();
 
  Locale miLocale = getLocale(appIdioma);
  ResourceBundle rb = ResourceBundle.getBundle("BasicConsoleLoginMessages", miLocale);
 
  System.out.println(rb.getString("LanguageSelection") + miLocale.getLanguage() + "_" + miLocale.getCountry());
  System.out.println(rb.getString("InputMessage"));
  String nombre= readInput();
 
  System.out.println(rb.getString("OutputMessage") + nombre);
  System.out.println(rb.getString("AcknowledgeMessage"));
 
 }

El punto clave es la recuperación del bundle correspondiente al idioma que elija el usuario. Esta recuperación se hace a través de la clase java.util.ResourceBundle, el único requisito es que los ficheros de los ‘bundles’ sean accesibles desde el classloader de la aplicación.

 ResourceBundle rb = ResourceBundle.getBundle("BasicConsoleLoginMessages", miLocale);

Una vez accedido el bundle, recuperar los mensajes de texto es una sencilla llamada al método getString(String clave);

rb.getString("OutputMessage")

Conclusión

La internacionalización no aporta excesiva dificultad adicional al código y permite traducir las aplicaciones a diferentes idiomas de forma muy sencilla.

Esta aplicación que he utilizado en el ejemplo es una aplicación excesivamente sencilla y no ilustra la dificultad que puede llegar a ser internacionalizar una aplicación no preparada para ello. En las aplicaciones complejas se hace necesario sino obligatorio un diseño inicial i18n, ya que incorporarlo posteriormente puede darnos más de un quebradero de cabeza.

Links

Minientrada

Error increible de Java con Windows 7

En el mundillo Java los fallos a veces pasan, y a veces pasan sin ser culpa de Java. Y esas veces hay que celebrarlo, porque son las menos.

Coincidiendo con la migración de Windows XP a Windows 7 en los entornos de usuario, una de nuestras aplicaciones java realizada con tecnología RCP (Rich Client Platform), decidió sin previo aviso que no quería arrancar. Los síntomas eran claros, al pinchar sobre el ejecutable, en nuestra recién estrenada instalación de Windows 7, la aplicación comenzaba el arranque y se cerraba sin llegar a mostrar la ventana inicial. Lo que nos extrañaba es que la misma versión de la aplicación en Windows XP funcionara correctamente.

El error.

En una aplicación cliente, relativamente «compleja», como es ésta; con accesos a base de datos mediante JDBC, con clientes EJB que se conectan a EJBs publicados en un servidor J2EE, e informes generados con BIRT, muchas variables podían influir en el mal funcionamiento. Lo que no sospechaba, ni por asomo, es que algo tan tonto como el tipo de letra pudiera influir en una aplicación java, sin embargo la ejecución de la aplicación RCP era clara al respecto ya que las trazas dejaban el siguiente rastro:

java.util.MissingResourceException: Wrong font data format. Value is: «MS Sans Serif-negrita-12 «

at org.eclipse.jface.resource.FontRegistry.makeFontData(FontRegistry.java:767)

at org.eclipse.jface.resource.FontRegistry.readResourceBundle(FontRegistry.java:860)

at org.eclipse.jface.resource.FontRegistry.readResourceBundle(FontRegistry.java:342)

at org.eclipse.jface.resource.FontRegistry.(FontRegistry.java:286)

at org.eclipse.jface.resource.FontRegistry.(FontRegistry.java:308)

at org.eclipse.jface.resource.JFaceResources.getFontRegistry(JFaceResources.java:342)

 

Windows 7 y los DPI de las fuentes.

 

Los monitores planos (LCD, TFT…) tienen un tamaño fijo de pixel o «resolución» con la que muestran los elementos en pantalla. Cuando un usuario, desde el sistema operativo, cambia a una resolución menor para ver más grande el tamaño de la letra, el monitor combina y ajusta los pixeles de tamaño fijo para conseguir la nueva resolución. Este proceso degrada la calidad de la imagen vista en pantalla haciendo que todo, no solo el texto, se vea peor.

Para evitar esto, Windows permite el aumento de tamaño de las fuentes y de las ventanas manteniendo la resolución nativa del monitor. Gracias a este método se obtienen imágenes más nítidas al poder utilizar el tamaño original de los píxeles para componerlas.

En Windows 7, para hacer que la resolución del sistema operativo corresponda con la resolución nativa de la pantalla y en ciertas resoluciones altas, para que las letras se muestren a un tamaño aceptable establece por defecto la configuración 120 DPI (dots per inch) de las fuentes. La configuración normal de las fuentes es 96 DPI. Esto implica que en 120DPI se usa un 125% de DPI más grande que lo habitual.

Si el usuario vuelve a 96 DPI, es decir, vuelve al tamaño de fuente normal, las fuentes TrueType, que permiten escalado, se ajustan automáticamente. Sin embargo, las fuentes basadas en bitmaps (imágenes), como MS Sans Serif, deben utilizar el fichero de fuentes adecuado a la resolución. Por tanto, en la instalación de Windows 7 debería existirá un fichero de fuentes para 96DPI y otro para 120DPI.

Sin embargo, si a la hora de instalar Windows 7 la resolución de pantalla es una resolución alta (por ejemplo, 1900×1200) se establece las fuentes por defecto a 120 DPI y no se instalan las fuentes 96 DPI ya que se considera que no se van a utilizar.

Por tanto, si se utiliza el tamaño 96DPI, se encontrará con que para ciertos tipos de letra los ficheros no existen, y como en nuestro caso, dará un error. La aplicación RCP utiliza un sistema de ventanas que lo independiza del sistema operativo. Este sistema de ventanas es conocido como JFace y está basado en SWT (Standar Widget Tookit). Este sistema utiliza las fuentes normales de 96DPI y todo junto provoca el fallo.

Las soluciones. Considerando las particularidades de nuestro entorno se puede optar por varias soluciones. A continuación se muestran en orden de prioridad:

 

  • Incluir las fuentes 96DPI en la instalación de Windows 7.
  • Cambiar el tipo de fuente que utiliza la aplicación RCP para que use una fuente TrueType (que no dan problemas en el escalado).
  • Parchear Windows 7 para que utilice las fuentes 120DPI cuando de fuentes bitmap se trata.

 

Incluir las fuentes 96DPI en la instalacion de Windows 7 Las fuentes en Windows 7 se instalan de diferentes maneras. El método más sencillo es descargar la fuente, descomprimirla, hacer doble click en el archivo .FON y finalmente hacer click en el botón «Instalar».

Las fuentes se pueden descargar desde los siguentes links:

Cambiar el tipo de fuente de la aplicación RCP

La aplicación RCP utiliza jFace para la implementación de la interfaz de usuario. Es en esta interfaz donde se configuran los tipos de fuente que se utilizan en la aplicación. La configuración se realiza en el fichero jfacefonts_es.properties que se encuentra en la ruta \\plugins\org.eclipse.jface.nl_es_0.2.0.v20080615043401\org\eclipse\jface\resource

Estos ficheros de propiedes contienen el tipo de fuentes que se utilizan en diferentes tipos de controles pertenecientes a la interfaz de usuario. Es en este fichero donde estará especificado el tipo de letra Sans Serif que tendremos que sustituir por un tipo de letra TrueType.

En nuestro caso el cambio realizado es el siguiente:

org.eclipse.jface.bannerfont.0=Tahoma-bold-8

org.eclipse.jface.headerfont.0=Tahoma-bold-12

Parchear Windows 7 para que utilice las fuentes 120DPI.

Parchear Windows 7 implica tocar el registro. Esta opción, aunque válida, no es recomendable pues lo que se hace es trampear el sistema. La trampa consiste en que para determinados casos, en vez de utilizar el fichero de fuentes que debería utilizarse (96DPI), que es el correcto pero no está instalado, se utilice el fichero de fuentes incorrecto pero existente en el sistema (el de 120DPI).

Aún así, dejo aquí la solución por si alguien tiene prisa en sacar adelante su programa y no tiene tiempo de aplicar las soluciones anteriormente recomendadas.

Lo primero que hay que hacer es acceder al registro de Windows 7 utilizando la herramienta regedit.

Una vez dentro del editor del registro se busca la clave MS Sans Serif 8,10,12,14,18,24 que se encuentra en la ruta HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts\.

El truco es cambiar el valor existente en esa entrada, SSERIFF.FON por el valor SSERIFE.FON

is set to SSERIFF.FON on a system which started at 125% DPI. The setting is set to SSERIFE.FON on a system which started at 100% DPI. Notice that one character of the file name changes from F to E.

The actual font files used might be different on Windows systems for other languages or code pages. See the table below for the file names:

While we are fixing the MS Sans Serif font, we can also fix the MS Serif and Courier fonts. These are the Registry settings MS Serif 8,10,12,14,18,24 and Courier 10,12,15 values in the same registry key. See the table below for the file names:

To fix the system you need to change the settings to new file names (changing the appropriate letter from F to E) and reboot.

Note: The registry changes do not take effect until the system has been restarted. You MUST reboot after making the registry changes.

Below are the contents of Font Fix.reg file to make the changes for an English system:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts] «MS Sans Serif 8,10,12,14,18,24″=»SSERIFE.FON» «MS Serif 8,10,12,14,18,24″=»SERIFE.FON» «Courier 10,12,15″=»COURE.FON»

Links. Windows 7, bitmap fonts and Microsoft Dynamics GP