Return-Path: Delivered-To: apmail-incubator-geronimo-dev-archive@incubator.apache.org Received: (qmail 52697 invoked by uid 500); 23 Aug 2003 20:54:18 -0000 Mailing-List: contact geronimo-dev-help@incubator.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: list-post: Reply-To: geronimo-dev@incubator.apache.org Delivered-To: mailing list geronimo-dev@incubator.apache.org Received: (qmail 52638 invoked from network); 23 Aug 2003 20:54:16 -0000 Received: from cerberus.uk.clara.net (195.8.69.103) by daedalus.apache.org with SMTP; 23 Aug 2003 20:54:16 -0000 Received: from du-037-0214.access.clara.net ([217.158.29.214] helo=tackline.demon.co.uk) by cerberus.uk.clara.net with esmtp (Exim 4.22) id 19qeGV-0005uS-0o for geronimo-dev@incubator.apache.org; Sat, 23 Aug 2003 20:41:35 +0100 Message-ID: <3F47C2C4.6010300@tackline.demon.co.uk> Date: Sat, 23 Aug 2003 20:38:44 +0100 From: Thomas Hawtin User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2.1) Gecko/20030225 X-Accept-Language: en-us, en MIME-Version: 1.0 To: geronimo-dev@incubator.apache.org Subject: Re: [i18n] Hardcoded message strings References: <9361E73C-D225-11D7-870E-0003934D3EA4@ioshq.com> In-Reply-To: <9361E73C-D225-11D7-870E-0003934D3EA4@ioshq.com> Content-Type: multipart/mixed; boundary="------------080007030802060902010506" X-Spam-Rating: daedalus.apache.org 1.6.2 0/1000/N This is a multi-part message in MIME format. --------------080007030802060902010506 Content-Type: text/plain; charset=us-ascii; format=flowed Content-Transfer-Encoding: 7bit Alex Blewitt wrote: > > I think the only way of achieving this would be to have a > formatExceptionInLocale(Locale) method which could choose the > translation at print time, rather than at construction time. I guess getLocalizedMessage() should print a message localized for the environment it is called on, rather than the environment the exception was originally created in. The exception may o fcourse have been serialized. Then there's the question as to whether the message should be looked up on the constructing and printing machine. Anyway I've updated my code to, amongst other things, use XSLT (Exception.xsl should be applied to exception-list.xml with Exceptions.xml being picked up by document()). It should be easy enough to create as many versions of the code as you want. Perhaps one per package. Perhaps coarser. May want to split out exception subclasses so that they are reused between different "Exceptions" factory classes. Pros: o Significantly simplifies exception (re)throwing code. o More obvious when throwing code is doing something unusual. o I18n, chaining and logging all under one roof. Cons: o Adds to complexity of build process. o The XSLT is pretty icky (although you don't need to so much as look at that to change the supported exceptions or the code template). o Who wants i18n of exception messages anyway? Tom Hawtin --------------080007030802060902010506 Content-Type: text/xml; name="Exceptions.xml" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="Exceptions.xml" package ; ]]>]]> ]]> *

Typical use: *

 } catch (ServiceException exc) {
 *      throw Exceptions.newMyException(exc, "nastyMsgKey");
 * }
* *

This currently assumes one large resource bungle * with all messages for project (ever). * Might be better off with an instance approach. */ ]]>public class {MessageFormat instances are cached. */ private static final boolean lazy = true; /** * Indicates whether lazily created exceptions * should skip the stack trace fill in from their super constructor. * This is a highly expensive operation, but also a very useful one. * It is not recommended that this option is set to true. */ private static final boolean lazySkipFillInStackTrace = false; /** * Resources for error messages. */ private static final java.util.ResourceBundle resourceMsgs = java.util.ResourceBundle.getBundle(]]>""Not overly convinced this is the right way to do it. * Might be better off with a different formatting method. * Could create formatter each time and not cache. * Could map straight from key the formatter. * SoftReference should be a good enough. */ private static final ThreadLocal/*< SoftReference>> >*/ formatMaps = new ThreadLocal(); /** * Message if message key is not present in resource bundle. * Parameter 0 is set to the key. */ private static final String messageKeyUnknown = resourceMsgs.getString("messageKeyUnknown"); /** * Message if message in resource bundle is not a String. * Parameter 0 is set to the key. * Parameter 1 is set to the message.toString(). */ private static final String messageNotString = resourceMsgs.getString("messageNotString"); ]]>null indicates no message. * If first character is a colon, the rest of the key is taken as the message. */ ]]>public static ( final String key ) { return (key, (Object[])null, ()null); }null indicates no message. * If first character is a colon, the rest of the key is taken as the message. * @param cause Exception that caused this exception. * null indicates this exception was not caused by another. */ ]]>public static ( final String key, cause ) { return (key, (Object[])null, cause); }null indicates no message. * If first character is a colon, the rest of the key is taken as the message. * @param params Parameters for error message. * null indicates no parameters. */ ]]>public static ( final String key, Object[] params ) { return (key, params, ()null); }null indicates no message. * If first character is a colon, the rest of the key is taken as the message. * @param params Parameters for error message. * null indicates no parameters. * @param cause Exception that caused this exception. * null indicates this exception was not caused by another. ]]> */ public static ( String key, Object[] params, cause ) { if (lazy) { return new (key, params, cause); } else { String message = formatKey(Locale.US, key, params); exception = new ( message, cause ); exception.initCause(cause); return exception; } }Perhaps should look up no-localised message * immediately/on serialization? */ ]]>private static class extends implements LocalizedMessage { public ( String key, Object[] params, cause ) { // We supply a null message as well, // for the benefit of java.rmi.RemoteException. super((String)null, cause); // We supply a null message as well, // for simplicity. super((String)null); this.key = key; // Do we want to clone the elements as well here? // For instance Date, or indeed perhaps anything not in java.lang? this.params = (params==null || params.length==0) ? emptyObjectArray : (Object[])params.clone(); // Further calls to fillInStackTrace() should work. this.skipFillInStackTrace = false; initCause(cause); }null indicates no message. * If first character is a colon, the rest of the key is taken as the message. * @param params Parameters for message. * null indicates no parameters. * @return Formatted message. * null iff key is null. */ private static String formatKey( Locale locale, String key, Object[] params ) { if (key == null) { return null; } if (key.length() >= 1 && key.charAt(0) == ':') { return formatMessage(locale, key.substring(1), params); } try { try { String message = resourceMsgs.getString(key); return formatMessage(locale, message, params); } catch (java.lang.ClassCastException exc) { Object message = resourceMsgs.getObject(key); return formatMessage(locale, messageNotString, new Object[] { key, message==null ? null : message.toString() }); } } catch (java.util.MissingResourceException exc) { return formatMessage(locale, messageKeyUnknown, new Object[] { key }); } } /** * Format message. * @param locale Locale to format message into. * @param message In java.text.MessageFormat format. * null indicates no message. * @param params Parameters for message. * null indicates no parameters. * @return Formatted message. */ private static String formatMessage( Locale locale, String message, Object[] params ) { if (message == null) { return message; } // Short cut if no parameter or quote substitution required. if (message.indexOf('{') == -1 && message.indexOf('\'') == -1) { return message; } // Get, or create, map of locales to map of keys to formatters. SoftReference/*>*/ ref = (SoftReference)formatMaps.get(); Map/*>*/ formatLocaleMap = ref==null ? null : (Map)ref.get(); if (formatLocaleMap == null) { formatLocaleMap = new java.util.HashMap(); formatMaps.set(new SoftReference(formatLocaleMap)); } // Get, or create, locale's map of keys to formatters. Map/*>*/ formatMap = ref==null ? null : (Map)formatLocaleMap.get(locale); if (formatMap == null) { formatMap = new java.util.HashMap(); formatLocaleMap.put(locale, formatMap); } // Get, or create, format map. MessageFormat format = (MessageFormat)formatMap.get(message); if (format == null) { format = new MessageFormat(message, locale); formatMap.put(message, format); } return format.format( message, params==null ? emptyObjectArray : params ).toString(); } } ]]> --------------080007030802060902010506 Content-Type: text/plain; name="exceptions-code.dtd" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="exceptions-code.dtd" --------------080007030802060902010506 Content-Type: text/xml; name="exception-list.xml" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="exception-list.xml" --------------080007030802060902010506 Content-Type: text/plain; name="exception-list.dtd" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="exception-list.dtd" --------------080007030802060902010506 Content-Type: text/xml; name="Exceptions.xsl" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="Exceptions.xsl" --------------080007030802060902010506 Content-Type: text/plain; name="exceptions.properties" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="exceptions.properties" messageKeyUnknown=[Unkown message key ''{0}''] messageNotString=[Message ''{1}'' for key ''{0}'' is not a String] --------------080007030802060902010506 Content-Type: text/plain; name="LocalizedMessage.java" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="LocalizedMessage.java" package org.apache.geronimo.utils; /** * For objects (typically exceptions) that can return locale dependent messages. */ public interface LocalizedMessage { public String getLocalizedMessage(java.util.Locale locale); } --------------080007030802060902010506--