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