/** * Copyright (C) 2012 cogroo <cogroo@cogroo.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cogroo.addon; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; /** * The <code>InternationalizedException</code> class adds internationalization * support to the standard functionality provided by * <code>java.lang.Exception</code>. * <p> * To support internationalization, the thrower of an exception must not specify * a hardcoded message string. Instead, the thrower specifies a key that * identifies the message. That key is then looked up in a locale-specific * {@link java.util.ResourceBundle ResourceBundle} to find the actual message * associated with this exception. * <p> * This class also supports arguments to messages. The full message will be * constructed using the {@link java.text.MessageFormat MessageFormat} class. * For more information on internationalization, see the <a * href="http://java.sun.com/j2se/1.4/docs/guide/intl/index.html"> Java * Internationalization Guide</a>. * <p> * This version of this class works with JDK versions prior to 1.4, since it * does not assume support for exception chaining. The file * <code>InternationalizedException.java_1_4</code> is a version that uses the * exception chaining support built-in to JDK1.4. * * */ public class InternationalizedException extends Exception { /** * The base name of the resource bundle in which the message for this * exception is located. */ private String mResourceBundleName; /** * An identifier that maps to the message for this exception. */ private String mMessageKey; /** * The arguments to this exception's message, if any. This allows an * <code>InternationalizedException</code> to have a compound message, made * up of multiple parts that are concatenated in a language-neutral way. */ private Object[] mArguments; /** * The exception that caused this exception to occur. */ private Throwable mCause; /** * Creates a new <code>InternationalizedException</code> with a null * message. */ public InternationalizedException() { this(null, null, null, null); } /** * Creates a new <code>InternationalizedException</code> with the specified * cause and a null message. * * @param aCause * the original exception that caused this exception to be thrown, * if any */ public InternationalizedException(Throwable aCause) { this(null, null, null, aCause); } /** * Creates a new <code>InternationalizedException</code> with the specified * message. * * @param aResourceBundleName * the base name of the resource bundle in which the message for * this exception is located. * @param aMessageKey * an identifier that maps to the message for this exception. The * message may contain placeholders for arguments as defined by the * {@link java.text.MessageFormat MessageFormat} class. * @param aArguments * The arguments to the message. <code>null</code> may be used if * the message has no arguments. */ public InternationalizedException(String aResourceBundleName, String aMessageKey, Object[] aArguments) { this(aResourceBundleName, aMessageKey, aArguments, null); } /** * Creates a new <code>InternationalizedException</code> with the specified * message and cause. * * @param aResourceBundleName * the base name of the resource bundle in which the message for * this exception is located. * @param aMessageKey * an identifier that maps to the message for this exception. The * message may contain placeholders for arguments as defined by the * {@link java.text.MessageFormat MessageFormat} class. * @param aArguments * The arguments to the message. <code>null</code> may be used if * the message has no arguments. * @param aCause * the original exception that caused this exception to be thrown, * if any */ public InternationalizedException(String aResourceBundleName, String aMessageKey, Object[] aArguments, Throwable aCause) { super(); mCause = aCause; mResourceBundleName = aResourceBundleName; mMessageKey = aMessageKey; mArguments = aArguments; // if null message and mCause is Internationalized exception, "promote" // message if (mResourceBundleName == null && mMessageKey == null) { if (mCause instanceof InternationalizedException) { mResourceBundleName = ((InternationalizedException) mCause) .getResourceBundleName(); mMessageKey = ((InternationalizedException) mCause).getMessageKey(); mArguments = ((InternationalizedException) mCause).getArguments(); } else if (mCause instanceof InternationalizedRuntimeException) { mResourceBundleName = ((InternationalizedRuntimeException) mCause) .getResourceBundleName(); mMessageKey = ((InternationalizedRuntimeException) mCause) .getMessageKey(); mArguments = ((InternationalizedRuntimeException) mCause) .getArguments(); } } } /** * Gets the base name of the resource bundle in which the message for this * exception is located. * * @return the resource bundle base name. May return <code>null</code> if * this exception has no message. */ public String getResourceBundleName() { return mResourceBundleName; } /** * Gets the identifier for this exception's message. This identifier can be * looked up in this exception's * {@link java.util.ResourceBundle ResourceBundle} to get the locale-specific * message for this exception. * * @return the resource identifier for this exception's message. May return * <code>null</code> if this exception has no message. */ public String getMessageKey() { return mMessageKey; } /** * Gets the arguments to this exception's message. Arguments allow a * <code>InternationalizedException</code> to have a compound message, made * up of multiple parts that are concatenated in a language-neutral way. * * @return the arguments to this exception's message. */ public Object[] getArguments() { if (mArguments == null) return new Object[0]; Object[] result = new Object[mArguments.length]; System.arraycopy(mArguments, 0, result, 0, mArguments.length); return result; } /** * Gets the <i>English</i> detail message for this exception. For the * localized message use {@link #getLocalizedMessage()}. * * @return the English detail message for this exception. */ public String getMessage() { return getLocalizedMessage(Locale.ENGLISH); } /** * Gets the localized detail message for this exception. This uses the * default Locale for this JVM. A Locale may be specified using * {@link #getLocalizedMessage(Locale)}. * * @return this exception's detail message, localized for the default Locale. */ public String getLocalizedMessage() { return getLocalizedMessage(Locale.getDefault()); } /** * Gets the localized detail message for this exception using the specified * <code>Locale</code>. * * @param aLocale * the locale to use for localizing the message * * @return this exception's detail message, localized for the specified * <code>Locale</code>. */ public String getLocalizedMessage(Locale aLocale) { // check for null message if (getMessageKey() == null) return null; try { // locate the resource bundle for this exception's messages // turn over the classloader of the current object explicitly, so that the // message resolving also works for derived exception classes ResourceBundle bundle = ResourceBundle.getBundle( getResourceBundleName(), aLocale, this.getClass() .getClassLoader()); // retrieve the message from the resource bundle String message = bundle.getString(getMessageKey()); // if arguments exist, use MessageFormat to include them if (getArguments().length > 0) { MessageFormat fmt = new MessageFormat(message); fmt.setLocale(aLocale); return fmt.format(getArguments()); } else return message; } catch (Exception e) { return "EXCEPTION MESSAGE LOCALIZATION FAILED: " + e.toString(); } } /** * Gets the cause of this Exception. * * @return the Throwable that caused this Exception to occur, if any. Returns * <code>null</code> if there is no such cause. */ public Throwable getCause() { return mCause; } /** * Checks if this exception, or any of its root causes, has a particular UIMA * message key. This allows checking for particular error condition * * @param messageKey * to search for in the exception chain * @return true if this exception or any of its root causes has a particular UIMA message key. */ public boolean hasMessageKey(String messageKey) { if (messageKey.equals(this.getMessageKey())) { return true; } Throwable cause = getCause(); if (cause != null && cause instanceof InternationalizedException) { return ((InternationalizedException) cause).hasMessageKey(messageKey); } return false; } public synchronized Throwable initCause(Throwable cause) { mCause = cause; return this; } }