/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: ExceptionCommon.java,v 1.1.1.1.2.2 2004/07/10 03:34:52 vlad_r Exp $ */ package com.vladium.util.exception; import java.io.PrintStream; import java.io.PrintWriter; import java.text.MessageFormat; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import com.vladium.util.IJREVersion; // TODO: embed build # in error codes // ---------------------------------------------------------------------------- /** * TODO: javadoc * * Based on code <a href="http://www.fawcette.com/javapro/2002_12/online/exception_vroubtsov_12_16_02/default_pf.asp">published</a> * by me in JavaPro, 2002.<P> * * This non-instantiable class provides static support functions common to * {@link AbstractException} and {@link AbstractRuntimeException}.<P> * * @author Vlad Roubtsov, (C) 2002 */ abstract class ExceptionCommon implements IJREVersion { // public: ................................................................ /** * This method can be called by static initializers of {@link AbstractException} * and {@link AbstractRuntimeException} subclasses in order to add another * resource bundle to the set that is used to look up error codes. This makes * it possible to extend the set of exception error codes across independently * maintained and built projects.<P> * * <BLOCKQUOTE> * Note that this introduces a possibility of error code name clashes. This * is resolved in the following way: * <UL> * <LI> when <CODE>getMessage(namespace, code)</CODE> is called, 'code' * is attempted to be looked up in the resource bundle previously keyed * under 'namespace'; * * <LI> if no such bundle it found or if it does not contain a value for * key 'code', the same step is repeated for the superclass of 'namespace'; * * <LI> finally, if all of the above steps fail, the root resource bundle * specified by {@link #ROOT_RESOURCE_BUNDLE_NAME} is searched. * </UL> * * This strategy ensures that error codes follow inheritance and hiding rules * similar to Java static methods.<P> * </BLOCKQUOTE> * * <B>IMPORTANT:</B> this method must be called from static class initializers * <I>only</I>.<P> * * There is no visible state change if the indicated resource is not found * or if it has been added already under the same key.<P> * * @param namespace the Class object acting as the namespace key for the * resource bundle identified by 'messageResourceBundleName'. <I>This should * be the calling class.</I> [the method is a no-op if this is null] * * @param messageResourceBundleName name of a bundle (path relative to 'namespace' * package) to add to the set from which error code mappings are retrieved * [the method is a no-op if this is null or an empty string] * * @return ResourceBundle that corresponds to 'namespace' key or null if * no such bundle could be loaded * * @throws Error if 'namespace' does not correspond to an exception class derived * from {@link AbstractException} or {@link AbstractRuntimeException}. * * @see #lookup */ public static ResourceBundle addExceptionResource (final Class namespace, final String messageResourceBundleName) { if ((namespace != null) && (messageResourceBundleName != null) && (messageResourceBundleName.length () > 0)) { // bail out if the some other exception hierarchy attempts // to use this functionality: if (! ABSTRACT_EXCEPTION.isAssignableFrom (namespace) && ! ABSTACT_RUNTIME_EXCEPTION.isAssignableFrom (namespace)) { throw new Error ("addExceptionResource(): class [" + namespace + "] is not a subclass of " + ABSTRACT_EXCEPTION.getName () + " or " + ABSTACT_RUNTIME_EXCEPTION.getName ()); } // try to load resource bundle ResourceBundle temprb = null; String nameInNamespace = null; try { nameInNamespace = getNameInNamespace (namespace, messageResourceBundleName); //temprb = ResourceBundle.getBundle (nameInNamespace); ClassLoader loader = namespace.getClassLoader (); if (loader == null) loader = ClassLoader.getSystemClassLoader (); temprb = ResourceBundle.getBundle (nameInNamespace, Locale.getDefault (), loader); } catch (Throwable ignore) { // ignored intentionally: if the exception codes rb is absent, // we are still in a supported configuration temprb = null; } if (temprb != null) { synchronized (s_exceptionCodeMap) { final ResourceBundle currentrb = (ResourceBundle) s_exceptionCodeMap.get (namespace); if (currentrb != null) return currentrb; else { s_exceptionCodeMap.put (namespace, temprb); return temprb; } } } } return null; } // protected: ............................................................. // package: ............................................................... static void printStackTrace (Throwable t, final PrintWriter out) { if (JRE_1_4_PLUS) { if (t instanceof IThrowableWrapper) { final IThrowableWrapper tw = (IThrowableWrapper) t; tw.__printStackTrace (out); } else { t.printStackTrace (out); } } else { for (boolean first = true; t != null; ) { if (first) first = false; else { out.println (); out.println (NESTED_THROWABLE_HEADER); } if (t instanceof IThrowableWrapper) { final IThrowableWrapper tw = (IThrowableWrapper) t; tw.__printStackTrace (out); t = tw.getCause (); } else { t.printStackTrace (out); break; } } } } static void printStackTrace (Throwable t, final PrintStream out) { if (JRE_1_4_PLUS) { if (t instanceof IThrowableWrapper) { final IThrowableWrapper tw = (IThrowableWrapper) t; tw.__printStackTrace (out); } else { t.printStackTrace (out); } } else { for (boolean first = true; t != null; ) { if (first) first = false; else { out.println (); out.println (NESTED_THROWABLE_HEADER); } if (t instanceof IThrowableWrapper) { final IThrowableWrapper tw = (IThrowableWrapper) t; tw.__printStackTrace (out); t = tw.getCause (); } else { t.printStackTrace (out); break; } } } } /** * Provides support for lookup of exception error codes from {@link AbstractException} * and {@link AbstractRuntimeException} and their subclasses. * * @param namespace the Class object acting as the key to the namespace from * which to retrieve the description for 'code' [can be null, in which case * only the root namespace is used for lookup] * * @param code the message string value that was passed into exception * constructor [can be null, in which case null is returned]. * * @return looked-up error message or the error code if it could not be * looked up [null is returned on null 'code' input only]. * * This method does not throw. * * @see AbstractException#getMessage * @see AbstractRuntimeException#getMessage */ static String getMessage (final Class namespace, final String code) { if (code == null) return null; try { if (code.length () > 0) { // look the code up in the resource bundle: final String msg = lookup (namespace, code); if (msg == null) { // if code lookup failed, return 'code' as is: return code; } else { return EMBED_ERROR_CODE ? "[" + code + "] " + msg : msg; } } else { return ""; } } catch (Throwable t) { // this method must never fail: default to returning the input // verbatim on all unexpected problems return code; } } /** * Provides support for lookup of exception error codes from {@link AbstractException} * and {@link AbstractRuntimeException} and their subclasses. * * @param namespace the Class object acting as the key to the namespace from * which to retrieve the description for 'code' [can be null, in which case * only the root namespace is used for lookup] * * @param code the message string value that was passed into exception * constructor [can be null, in which case null is returned]. * * @param arguments java.text.MessageFormat-style parameters to be substituted * into the error message once it is looked up. * * @return looked-up error message or the error code if it could not be * looked up [null is returned on null 'code' input only]. * * This method does not throw. * * @see AbstractException#getMessage * @see AbstractRuntimeException#getMessage */ static String getMessage (final Class namespace, final String code, final Object [] arguments) { if (code == null) return null; final String pattern = getMessage (namespace, code); // assertion: pattern != null if ((arguments == null) || (arguments.length == 0)) { return pattern; } else { try { return MessageFormat.format (pattern, arguments); } catch (Throwable t) { // this method cannot fail: default to returning the input // verbatim on all unexpected problems: final StringBuffer msg = new StringBuffer (code + EOL); for (int a = 0; a < arguments.length; a ++) { msg.append ("\t{" + a + "} = ["); final Object arg = arguments [a]; try { msg.append (arg.toString ()); } catch (Throwable e) // guard against bad toString() overrides { if (arg != null) msg.append (arg.getClass ().getName ()); else msg.append ("null"); } msg.append ("]"); msg.append (EOL); } return msg.toString (); } } } // private: ............................................................... private ExceptionCommon () {} // prevent subclassing /** * Internal property lookup method. It implements the lookup scheme described * in {@link #addExceptionResource}. * * @return property value corresponding to 'propertyName' [null if lookup fails] */ private static String lookup (Class namespace, final String propertyName) { if (propertyName == null) return null; // note: the following does not guard against exceptions that do not subclass // our base classes [done elsewhere], however it will not crash either // check extension bundles: if (namespace != null) { ResourceBundle rb; while (namespace != ABSTRACT_EXCEPTION && namespace != ABSTACT_RUNTIME_EXCEPTION && namespace != THROWABLE && namespace != null) { synchronized (s_exceptionCodeMap) { rb = (ResourceBundle) s_exceptionCodeMap.get (namespace); if (rb == null) { // check if there is a default bundle to be loaded for this namespace: if ((rb = addExceptionResource (namespace, "exceptions")) == null) { // add an immutable empty bundle to avoid this check in the future: s_exceptionCodeMap.put (namespace, EMPTY_RESOURCE_BUNDLE); } } } if (rb != null) { String propertyValue = null; try { propertyValue = rb.getString (propertyName); } catch (Throwable ignore) {} if (propertyValue != null) return propertyValue; } // walk the inheritance chain for 'namespace': namespace = namespace.getSuperclass (); } } // if everything fails, check the root bundle: if (ROOT_RESOURCE_BUNDLE != null) { String propertyValue = null; try { propertyValue = ROOT_RESOURCE_BUNDLE.getString (propertyName); } catch (Throwable ignore) {} if (propertyValue != null) return propertyValue; } return null; } private static String getNameInNamespace (final Class namespace, final String name) { if (namespace == null) return name; final String namespaceName = namespace.getName (); final int lastDot = namespaceName.lastIndexOf ('.'); if (lastDot <= 0) return name; else return namespaceName.substring (0, lastDot + 1) + name; } // changes this to 'false' to eliminate repetition of error codes in // the output of getMessage(): private static final boolean EMBED_ERROR_CODE = true; // the name of the 'root' message resource bundle, derived as // [this package name + ".exceptions"]: private static final String ROOT_RESOURCE_BUNDLE_NAME; // set in <clinit> // the root resource bundle; always checked if all other lookups fail: private static final ResourceBundle ROOT_RESOURCE_BUNDLE; // set in <clinit> // static cache of all loaded resource bundles, populated via addExceptionResource(): private static final Map /* Class -> ResourceBundle */ s_exceptionCodeMap = new HashMap (); // misc constants: private static final String NESTED_THROWABLE_HEADER = "[NESTED EXCEPTION]:"; private static final Class THROWABLE = Throwable.class; private static final Class ABSTRACT_EXCEPTION = AbstractException.class; private static final Class ABSTACT_RUNTIME_EXCEPTION = AbstractRuntimeException.class; /*private*/ static final Enumeration EMPTY_ENUMERATION = Collections.enumeration (Collections.EMPTY_SET); private static final ResourceBundle EMPTY_RESOURCE_BUNDLE = new ResourceBundle () { public Object handleGetObject (final String key) { return null; } public Enumeration getKeys () { return EMPTY_ENUMERATION; } }; // end-of-line terminator for the current platform: private static final String EOL = System.getProperty ("line.separator", "\n"); static { // set the name of ROOT_RESOURCE_BUNDLE_NAME: ROOT_RESOURCE_BUNDLE_NAME = getNameInNamespace (ExceptionCommon.class, "exceptions"); // set the root resource bundle: ResourceBundle temprb = null; try { temprb = ResourceBundle.getBundle (ROOT_RESOURCE_BUNDLE_NAME); } catch (Throwable ignore) { // if the exception codes rb is absent, we are still in a supported configuration } ROOT_RESOURCE_BUNDLE = temprb; } } // end of class // ----------------------------------------------------------------------------