Home | History | Annotate | Download | only in exception
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: ExceptionCommon.java,v 1.1.1.1.2.2 2004/07/10 03:34:52 vlad_r Exp $
      8  */
      9 package com.vladium.util.exception;
     10 
     11 import java.io.PrintStream;
     12 import java.io.PrintWriter;
     13 import java.text.MessageFormat;
     14 import java.util.Collections;
     15 import java.util.Enumeration;
     16 import java.util.HashMap;
     17 import java.util.Locale;
     18 import java.util.Map;
     19 import java.util.ResourceBundle;
     20 
     21 import com.vladium.util.IJREVersion;
     22 
     23 // TODO: embed build # in error codes
     24 
     25 // ----------------------------------------------------------------------------
     26 /**
     27  * TODO: javadoc
     28  *
     29  * Based on code <a href="http://www.fawcette.com/javapro/2002_12/online/exception_vroubtsov_12_16_02/default_pf.asp">published</a>
     30  * by me in JavaPro, 2002.<P>
     31  *
     32  * This non-instantiable class provides static support functions common to
     33  * {@link AbstractException} and {@link AbstractRuntimeException}.<P>
     34  *
     35  * @author Vlad Roubtsov, (C) 2002
     36  */
     37 abstract class ExceptionCommon implements IJREVersion
     38 {
     39     // public: ................................................................
     40 
     41     /**
     42      * This method can be called by static initializers of {@link AbstractException}
     43      * and {@link AbstractRuntimeException} subclasses in order to add another
     44      * resource bundle to the set that is used to look up error codes. This makes
     45      * it possible to extend the set of exception error codes across independently
     46      * maintained and built projects.<P>
     47      *
     48      * <BLOCKQUOTE>
     49      * Note that this introduces a possibility of error code name clashes. This
     50      * is resolved in the following way:
     51      * <UL>
     52      *     <LI> when <CODE>getMessage(namespace, code)</CODE> is called, 'code'
     53      *     is attempted to be looked up in the resource bundle previously keyed
     54      *     under 'namespace';
     55      *
     56      *     <LI> if no such bundle it found or if it does not contain a value for
     57      *     key 'code', the same step is repeated for the superclass of 'namespace';
     58      *
     59      *     <LI> finally, if all of the above steps fail, the root resource bundle
     60      *     specified by {@link #ROOT_RESOURCE_BUNDLE_NAME} is searched.
     61      * </UL>
     62      *
     63      * This strategy ensures that error codes follow inheritance and hiding rules
     64      * similar to Java static methods.<P>
     65      * </BLOCKQUOTE>
     66      *
     67      * <B>IMPORTANT:</B> this method must be called from static class initializers
     68      * <I>only</I>.<P>
     69      *
     70      * There is no visible state change if the indicated resource is not found
     71      * or if it has been added already under the same key.<P>
     72      *
     73      * @param namespace the Class object acting as the namespace key for the
     74      * resource bundle identified by 'messageResourceBundleName'. <I>This should
     75      * be the calling class.</I> [the method is a no-op if this is null]
     76      *
     77      * @param messageResourceBundleName name of a bundle (path relative to 'namespace'
     78      * package) to add to the set from which error code mappings are retrieved
     79      * [the method is a no-op if this is null or an empty string]
     80      *
     81      * @return ResourceBundle that corresponds to 'namespace' key or null if
     82      * no such bundle could be loaded
     83      *
     84      * @throws Error if 'namespace' does not correspond to an exception class derived
     85      * from {@link AbstractException} or {@link AbstractRuntimeException}.
     86      *
     87      * @see #lookup
     88      */
     89     public static ResourceBundle addExceptionResource (final Class namespace,
     90                                                        final String messageResourceBundleName)
     91     {
     92         if ((namespace != null) && (messageResourceBundleName != null)
     93             && (messageResourceBundleName.length () > 0))
     94         {
     95             // bail out if the some other exception hierarchy attempts
     96             // to use this functionality:
     97             if (! ABSTRACT_EXCEPTION.isAssignableFrom (namespace)
     98                 && ! ABSTACT_RUNTIME_EXCEPTION.isAssignableFrom (namespace))
     99             {
    100                 throw new Error ("addExceptionResource(): class [" + namespace +
    101                     "] is not a subclass of " + ABSTRACT_EXCEPTION.getName () +
    102                     " or " + ABSTACT_RUNTIME_EXCEPTION.getName ());
    103             }
    104 
    105             // try to load resource bundle
    106 
    107             ResourceBundle temprb = null;
    108             String nameInNamespace = null;
    109             try
    110             {
    111                 nameInNamespace = getNameInNamespace (namespace, messageResourceBundleName);
    112 
    113                 //temprb = ResourceBundle.getBundle (nameInNamespace);
    114 
    115                 ClassLoader loader = namespace.getClassLoader ();
    116                 if (loader == null) loader = ClassLoader.getSystemClassLoader ();
    117 
    118                 temprb = ResourceBundle.getBundle (nameInNamespace, Locale.getDefault (), loader);
    119             }
    120             catch (Throwable ignore)
    121             {
    122                // ignored intentionally: if the exception codes rb is absent,
    123                // we are still in a supported configuration
    124                temprb = null;
    125             }
    126 
    127             if (temprb != null)
    128             {
    129                 synchronized (s_exceptionCodeMap)
    130                 {
    131                     final ResourceBundle currentrb =
    132                         (ResourceBundle) s_exceptionCodeMap.get (namespace);
    133                     if (currentrb != null)
    134                         return currentrb;
    135                     else
    136                     {
    137                         s_exceptionCodeMap.put (namespace, temprb);
    138                         return temprb;
    139                     }
    140                 }
    141             }
    142         }
    143 
    144         return null;
    145     }
    146 
    147     // protected: .............................................................
    148 
    149     // package: ...............................................................
    150 
    151 
    152     static void printStackTrace (Throwable t, final PrintWriter out)
    153     {
    154         if (JRE_1_4_PLUS)
    155         {
    156             if (t instanceof IThrowableWrapper)
    157             {
    158                 final IThrowableWrapper tw = (IThrowableWrapper) t;
    159 
    160                 tw.__printStackTrace (out);
    161             }
    162             else
    163             {
    164                 t.printStackTrace (out);
    165             }
    166         }
    167         else
    168         {
    169             for (boolean first = true; t != null; )
    170             {
    171                 if (first)
    172                     first = false;
    173                 else
    174                 {
    175                     out.println ();
    176                     out.println (NESTED_THROWABLE_HEADER);
    177                 }
    178 
    179                 if (t instanceof IThrowableWrapper)
    180                 {
    181                     final IThrowableWrapper tw = (IThrowableWrapper) t;
    182 
    183                     tw.__printStackTrace (out);
    184                     t = tw.getCause ();
    185                 }
    186                 else
    187                 {
    188                     t.printStackTrace (out);
    189                     break;
    190                 }
    191             }
    192         }
    193     }
    194 
    195 
    196     static void printStackTrace (Throwable t, final PrintStream out)
    197     {
    198         if (JRE_1_4_PLUS)
    199         {
    200             if (t instanceof IThrowableWrapper)
    201             {
    202                 final IThrowableWrapper tw = (IThrowableWrapper) t;
    203 
    204                 tw.__printStackTrace (out);
    205             }
    206             else
    207             {
    208                 t.printStackTrace (out);
    209             }
    210         }
    211         else
    212         {
    213             for (boolean first = true; t != null; )
    214             {
    215                 if (first)
    216                     first = false;
    217                 else
    218                 {
    219                     out.println ();
    220                     out.println (NESTED_THROWABLE_HEADER);
    221                 }
    222 
    223                 if (t instanceof IThrowableWrapper)
    224                 {
    225                     final IThrowableWrapper tw = (IThrowableWrapper) t;
    226 
    227                     tw.__printStackTrace (out);
    228                     t = tw.getCause ();
    229                 }
    230                 else
    231                 {
    232                     t.printStackTrace (out);
    233                     break;
    234                 }
    235             }
    236         }
    237     }
    238 
    239 
    240     /**
    241      * Provides support for lookup of exception error codes from {@link AbstractException}
    242      * and {@link AbstractRuntimeException} and their subclasses.
    243      *
    244      * @param namespace the Class object acting as the key to the namespace from
    245      * which to retrieve the description for 'code' [can be null, in which case
    246      * only the root namespace is used for lookup]
    247      *
    248      * @param code the message string value that was passed into exception
    249      * constructor [can be null, in which case null is returned].
    250      *
    251      * @return looked-up error message or the error code if it could not be
    252      * looked up [null is returned on null 'code' input only].
    253      *
    254      * This method does not throw.
    255      *
    256      * @see AbstractException#getMessage
    257      * @see AbstractRuntimeException#getMessage
    258      */
    259     static String getMessage (final Class namespace, final String code)
    260     {
    261         if (code == null) return null;
    262 
    263         try
    264         {
    265             if (code.length () > 0)
    266             {
    267                 // look the code up in the resource bundle:
    268                 final String msg = lookup (namespace, code);
    269                 if (msg == null)
    270                 {
    271                     // if code lookup failed, return 'code' as is:
    272                     return code;
    273                 }
    274                 else
    275                 {
    276                     return EMBED_ERROR_CODE ? "[" + code + "] " + msg : msg;
    277                 }
    278            }
    279            else
    280            {
    281                return "";
    282            }
    283         }
    284         catch (Throwable t)
    285         {
    286             // this method must never fail: default to returning the input
    287             // verbatim on all unexpected problems
    288             return code;
    289         }
    290     }
    291 
    292     /**
    293      * Provides support for lookup of exception error codes from {@link AbstractException}
    294      * and {@link AbstractRuntimeException} and their subclasses.
    295      *
    296      * @param namespace the Class object acting as the key to the namespace from
    297      * which to retrieve the description for 'code' [can be null, in which case
    298      * only the root namespace is used for lookup]
    299      *
    300      * @param code the message string value that was passed into exception
    301      * constructor [can be null, in which case null is returned].
    302      *
    303      * @param arguments java.text.MessageFormat-style parameters to be substituted
    304      * into the error message once it is looked up.
    305      *
    306      * @return looked-up error message or the error code if it could not be
    307      * looked up [null is returned on null 'code' input only].
    308      *
    309      * This method does not throw.
    310      *
    311      * @see AbstractException#getMessage
    312      * @see AbstractRuntimeException#getMessage
    313      */
    314     static String getMessage (final Class namespace, final String code, final Object [] arguments)
    315     {
    316         if (code == null) return null;
    317         final String pattern = getMessage (namespace, code);
    318 
    319         // assertion: pattern != null
    320 
    321         if ((arguments == null) || (arguments.length == 0))
    322         {
    323             return pattern;
    324         }
    325         else
    326         {
    327             try
    328             {
    329                 return MessageFormat.format (pattern, arguments);
    330             }
    331             catch (Throwable t)
    332             {
    333                 // this method cannot fail: default to returning the input
    334                 // verbatim on all unexpected problems:
    335 
    336                 final StringBuffer msg = new StringBuffer (code + EOL);
    337 
    338                 for (int a = 0; a < arguments.length; a ++)
    339                 {
    340                     msg.append ("\t{" + a + "} = [");
    341                     final Object arg = arguments [a];
    342                     try
    343                     {
    344                         msg.append (arg.toString ());
    345                     }
    346                     catch (Throwable e) // guard against bad toString() overrides
    347                     {
    348                         if (arg != null)
    349                             msg.append (arg.getClass ().getName ());
    350                         else
    351                             msg.append ("null");
    352                     }
    353                     msg.append ("]");
    354                     msg.append (EOL);
    355                 }
    356 
    357                 return msg.toString ();
    358             }
    359         }
    360     }
    361 
    362     // private: ...............................................................
    363 
    364 
    365     private ExceptionCommon () {} // prevent subclassing
    366 
    367     /**
    368      * Internal property lookup method. It implements the lookup scheme described
    369      * in {@link #addExceptionResource}.
    370      *
    371      * @return property value corresponding to 'propertyName' [null if lookup fails]
    372      */
    373     private static String lookup (Class namespace, final String propertyName)
    374     {
    375         if (propertyName == null) return null;
    376 
    377         // note: the following does not guard against exceptions that do not subclass
    378         // our base classes [done elsewhere], however it will not crash either
    379 
    380         // check extension bundles:
    381         if (namespace != null)
    382         {
    383             ResourceBundle rb;
    384             while (namespace != ABSTRACT_EXCEPTION && namespace != ABSTACT_RUNTIME_EXCEPTION
    385                    && namespace != THROWABLE && namespace != null)
    386             {
    387                 synchronized (s_exceptionCodeMap)
    388                 {
    389                     rb = (ResourceBundle) s_exceptionCodeMap.get (namespace);
    390                     if (rb == null)
    391                     {
    392                         // check if there is a default bundle to be loaded for this namespace:
    393                         if ((rb = addExceptionResource (namespace, "exceptions")) == null)
    394                         {
    395                             // add an immutable empty bundle to avoid this check in the future:
    396                             s_exceptionCodeMap.put (namespace, EMPTY_RESOURCE_BUNDLE);
    397                         }
    398                     }
    399                 }
    400 
    401                 if (rb != null)
    402                 {
    403                     String propertyValue = null;
    404                     try
    405                     {
    406                         propertyValue = rb.getString (propertyName);
    407                     }
    408                     catch (Throwable ignore) {}
    409                     if (propertyValue != null) return propertyValue;
    410                 }
    411 
    412                 // walk the inheritance chain for 'namespace':
    413                 namespace = namespace.getSuperclass ();
    414             }
    415         }
    416 
    417         // if everything fails, check the root bundle:
    418         if (ROOT_RESOURCE_BUNDLE != null)
    419         {
    420             String propertyValue = null;
    421             try
    422             {
    423                 propertyValue = ROOT_RESOURCE_BUNDLE.getString (propertyName);
    424             }
    425             catch (Throwable ignore) {}
    426             if (propertyValue != null) return propertyValue;
    427         }
    428 
    429         return null;
    430     }
    431 
    432     private static String getNameInNamespace (final Class namespace, final String name)
    433     {
    434         if (namespace == null) return name;
    435 
    436         final String namespaceName = namespace.getName ();
    437         final int lastDot = namespaceName.lastIndexOf ('.');
    438 
    439         if (lastDot <= 0)
    440             return name;
    441         else
    442             return namespaceName.substring (0, lastDot + 1)  + name;
    443     }
    444 
    445 
    446     // changes this to 'false' to eliminate repetition of error codes in
    447     // the output of getMessage():
    448     private static final boolean EMBED_ERROR_CODE = true;
    449 
    450     // the name of the 'root' message resource bundle, derived as
    451     // [this package name + ".exceptions"]:
    452     private static final String ROOT_RESOURCE_BUNDLE_NAME;      // set in <clinit>
    453 
    454     // the root resource bundle; always checked if all other lookups fail:
    455     private static final ResourceBundle ROOT_RESOURCE_BUNDLE;   // set in <clinit>
    456 
    457     // static cache of all loaded resource bundles, populated via addExceptionResource():
    458     private static final Map /* Class -> ResourceBundle */ s_exceptionCodeMap = new HashMap ();
    459 
    460     // misc constants:
    461 
    462     private static final String NESTED_THROWABLE_HEADER = "[NESTED EXCEPTION]:";
    463     private static final Class THROWABLE                    = Throwable.class;
    464     private static final Class ABSTRACT_EXCEPTION           = AbstractException.class;
    465     private static final Class ABSTACT_RUNTIME_EXCEPTION    = AbstractRuntimeException.class;
    466     /*private*/ static final Enumeration EMPTY_ENUMERATION  = Collections.enumeration (Collections.EMPTY_SET);
    467     private static final ResourceBundle EMPTY_RESOURCE_BUNDLE = new ResourceBundle ()
    468     {
    469         public Object handleGetObject (final String key)
    470         {
    471             return null;
    472         }
    473 
    474         public Enumeration getKeys ()
    475         {
    476             return EMPTY_ENUMERATION;
    477         }
    478     };
    479 
    480     // end-of-line terminator for the current platform:
    481     private static final String EOL = System.getProperty ("line.separator", "\n");
    482 
    483 
    484     static
    485     {
    486         // set the name of ROOT_RESOURCE_BUNDLE_NAME:
    487         ROOT_RESOURCE_BUNDLE_NAME = getNameInNamespace (ExceptionCommon.class, "exceptions");
    488 
    489         // set the root resource bundle:
    490         ResourceBundle temprb = null;
    491         try
    492         {
    493             temprb = ResourceBundle.getBundle (ROOT_RESOURCE_BUNDLE_NAME);
    494         }
    495         catch (Throwable ignore)
    496         {
    497             // if the exception codes rb is absent, we are still in a supported configuration
    498         }
    499         ROOT_RESOURCE_BUNDLE = temprb;
    500     }
    501 
    502 } // end of class
    503 // ----------------------------------------------------------------------------
    504