Trabajar con Excepciones
En la sección, Manejo de Errores utilizando Excepciones, mostramos como lanzar y capturar excepciones. Como podrás recordar, los mensajes de excepciones mostrados por los ejemplos de esta lección están en Inglés. Frecuentemente, los mensajes de excepciones sólo se utilizan para propósitos de depurado y nunca son vistos por lo usuarios finales. Sin embargo, si estos usuarios están expuestos a mensajes de excepciones, deberemos asegurarnos de que no están codificados en cualquier otro idioma.
En esta lección, daremos algunos trucos para hacer los mensajes de excepciones independientes de la Localidad. Los ejemplos de código de esta sección incluyen objetos ResourceBundle y MessageFormat. Si no lo has hecho anteriormente, deberías leer primero Aislar Objetos Específicos de la Localidad en un ResourceBundle, y Formateo de Mensajes.
Manejar Mensajes de Excepciones Codificados
Si el texto del mensaje de un subclase de Exception ha sido codificado, no puede ser localizado, Pero existe un atajo, que explicaremos en esta sección.
Supongamos que llamamos a un método que lanza una excepción, y queremos mostrar el mensaje de la excepción cuando ocurra el error. Si este método pertenece a una clase de un paquete escrito por otras personas, no tendremos ningún control sobre el texto del mensaje a menos que tengamos acceso al código fuente. Este puede ser un grave problema si el texto del mensaje ha sido codificado, porque no podrá ser traducido a otros idiomas. En el siguiente código de ejemplo, encontramos este problema cuando abrimos un fichero que no existe.
static public void defaultMessage() {
try {
FileInputStream in = new FileInputStream("vapor.txt");
}
catch (FileNotFoundException e) {
System.out.println("e.getMessage = " + e.getMessage());
System.out.println("e.getLocalizedMessage = " + e.getLocalizedMessage());
System.out.println("e.toString = " + e.toString());
}
}
La clausula Catch del método defaultMessage genera la siguiente salida.
e.getMessage = vapor.txt
e.getLocalizedMessage = vapor.txt
e.toString = java.io.FileNotFoundException: vapor.txt
Estos mensajes son inadecuados por varias razones. Primero, el texto del mensaje está codificado dentro de la clase FileNotFoundException y no puede ser localizado. Intentamos imprimir el String devuelto por getLocalizedMessage, pero como getLocalizedMessage no ha sido implementada, sólo devuelve el mismo mensaje que el método getMessage. Además, getMessage sólo devuelve el nombre del fichero, que por sí mismo no es un mensaje traducible. Aunque podríamos encontrar útil el tener el nombre del fichero durante la depuración, nuestros usuarios finales necesitarán un mensaje más informativo.
En general, los mensajes proporcionados por subclases de Exception, como FileNotFoundException, se han creado para programadores, no para usuarios finales. Por ejemplo, un procesador de textos no debería mostrar, "java.io.FileNotFoundException: vapor.txt," cuando el usuario trata de abrir un fichero que no existe. Este mensaje, aunque es técnicamente correcto, no tiene sentido para la mayoría de la gente. En su lugar, cuando el procesador de textos capturara la FileNotFoundException, debería mostrar este mensaje: "Cannot find the file named vapor.txt. Make sure the file exists."
En una aplicación como un procesador de textos, el texto devuelto por FileNotFoundException.getMessage no es apropiado para los usuarios finales. Deberíamos proporcionar un mensaje que sea fácil de entender, y que pueda ser traducido. Veremos como hacer esto con el siguiente ejemplo.
Como el texto del mensaje necesita se traducido, lo aislaremos en un ResourceBundle. Almacenaremos nuestros mensajes en un fichero de propiedades, construiremos un ResourceBundle llamado ExceptionBundle. Aquí tenemos el mensaje del fichero ExceptionBundle_en_US.properties.
template = Cannot find the file named {0}. Make sure the file exists.
En el siguiente código mostramos un mensaje sensible a la localidad cuando se captura FileNotFoundException. Primero creamos un ResourceBundle para la Localidad apropiada. Luego recuperamos el patrón del mensaje traducido desde el ResourceBundle. Finalmente, aplicamos el patrón del formato para insertar el nombre del fichero en el mensaje.
static public void customMessage(Locale currentLocale) {
System.out.println("Locale: " + currentLocale.toString());
String fileName = "vapor.txt";
try {
FileInputStream in = new FileInputStream(fileName);
}
catch (FileNotFoundException e) {
ResourceBundle messages =
ResourceBundle.getBundle("ExceptionBundle",currentLocale);
Object[] messageArguments = {fileName};
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
formatter.applyPattern(messages.getString("template"));
String errorOut = formatter.format(messageArguments);
System.out.println(errorOut);
}
}
La salida del método customMessage está bastante mejor que: "java.io.FileNotFoundException: vapor.txt."
Locale: en_US
Cannot find the file named vapor.txt. Make sure the file exists.
Locale: de_DE
Die Datei vapor.txt konnte nicht gefunden werden.
Stellen Sie sicher, daß die Datei existiert.
Crear Subclases de Exception Independientes de la Localidad
Si creamos una subclase de Exception, deberíamos asegurarnos de que no contiene un mensajes codificado en su interior. Nuestras subclases, deberían implementar el método getLocalizedMessage.
Si queremos crear un subclase de Exception cuando necesitamos manejar errores específicos de nuestras aplicaciones, podemos crear nuestras subclases independientes de la Localidad sobrescribiendo su método getLocalizedMessage. Cada método que sobrescribamos debería aislar el mensaje que devuelve en un ResourceBundle. Esto permitirá que el mensaje sea traducido a varios idiomas durante la localización.
En el siguiente ejemplo, veremos cómo crear una subclase de Exception que muestra un mensaje independiente de la Localidad. El código fuente de este programa está en estos dos ficheros. OverLimitException.java y
Account.java.
El programa Account simula una cuenta de crédito. Si este programa se excede el límite de crédito, recupera un mensaje localizado desde un ResourceBundle llamado ExceptionBundle, y luego muestra el mensaje. En el fichero ExceptionBundle_en_US.properties, hemos especificado este mensaje.
pattern = Negative balance of {0,number,currency} is not allowed.
El programa Account recupera el mensaje con el método getLocalizedMessage, que hemos implementado en una subclase de Exception llamada OverLimitException. El método getLocalizedMessage acepta un objeto Locale como parámetro. Especificamos la Localidad cuando recuperamos el patrón del mensaje desde el ResourceBundle, y también cuando definimos el objeto MessageFormat. Por lo tanto, nuestra clase OverLimitException es sensible a la Localidad.
public class OverLimitException extends Exception {
private double detail;
public OverLimitException (double amount) {
detail = amount;
}
public String getMessage() {
return getLocalizedMessage(Locale.getDefault());
}
public String getLocalizedMessage(Locale currentLocale) {
ResourceBundle messages =
ResourceBundle.getBundle("ExceptionBundle",currentLocale);
Object[] messageArguments = {new Float(detail)};
MessageFormat formatter = new MessageFormat("");
formatter.setLocale(currentLocale);
formatter.applyPattern(messages.getString("pattern"));
return formatter.format(messageArguments);
}
}
En la clase Account, el método withdraw lanza un OverLimitException si el nuevo balance excede el límite de crédito. En el siguiente código, observa que hemos pasado el parámetro value al constructor OverLimitException en la sentencia throw. El constructor asigna este parámetro al campo detail de la clase OverLimitException. El método getLocalizedMessage inserta una representación String del campo detail en el mensaje devuelto. El método withdraw es el siguiente.
public void withdraw(double amount) throws OverLimitException {
double value = balance - amount;
if (value < creditLimit) {
throw new OverLimitException(value);
}
else {
balance = value;
}
}
En el método main de la clase Account, llamamos al método withdraw y captura el OverLimitException siempre que se lance. La clausula catch imprime el String devuelto por getLocalizedMessage. Aquí tienes el método main.
static public void main(String[] args) {
Locale[] locales = {
new Locale("en","US"),
new Locale("de","DE")
};
Account credit = new Account();
credit.deposit(20.00f);
for (int k = 0; k < locales.length; k++) {
try {
credit.withdraw(1000.00f);
}
catch (OverLimitException e) {
System.out.println("Locale: " + locales[k].toString());
System.out.println(e.getLocalizedMessage(locales[k]));
}
} // for
}
El programa Account muestra dos mensajes localizados. No sólo el texto se muestra en el idioma correcto, si no que también el formato de moneda es el adecuado para cada Localidad..
% java Account
Locale: en_US
Negative balance of ($980.00) is not allowed.
Locale: de_DE
Ihr Konto um -980,00 DM zu überziehen ist nicht gestattet.