/* 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: AbstractRuntimeException.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $ */ package com.vladium.util.exception; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.PrintStream; import java.io.PrintWriter; // ---------------------------------------------------------------------------- /** * Based on code published by me in <a href="http://www.fawcette.com/javapro/2002_12/online/exception_vroubtsov_12_16_02/default_pf.asp">JavaPro, 2002</a>.<P> * * This unchecked exception class is designed as a base/expansion point for the * entire hierarchy of unchecked exceptions in a project.<P> * * It provides the following features: * <UL> * <LI> ability to take in compact error codes that map to full text messages * in a resource bundle loaded at this class' instantiation time. This avoids * hardcoding the error messages in product code and allows for easy * localization of such text if required. Additionally, these messages * can be parametrized in the java.text.MessageFormat style; * <LI> exception chaining in J2SE versions prior to 1.4 * </UL> * * See {@link AbstractException} for a checked version of the same class.<P> * * TODO: javadoc * * Each constructor that accepts a String 'message' parameter accepts an error * code as well. You are then responsible for ensuring that either the root * <CODE>com.vladium.exception.exceptions</CODE> resource bundle * or your project/exception class-specific resource bundle [see * <A HREF="#details">below</A> for details] contains a mapping for this error * code. When this lookup fails the passed String value itself will be used as * the error message.<P> * * All constructors taking an 'arguments' parameter supply parameters to the error * message used as a java.text.MessageFormat pattern.<P> * * Example: * <PRE><CODE> * File file = ... * try * ... * catch (Exception e) * { * throw new AbstractRuntimeException ("FILE_NOT_FOUND", new Object[] {file, e}, e); * } * </CODE></PRE> * where <CODE>com.vladium.util.exception.exceptions</CODE> contains: * <PRE><CODE> * FILE_NOT_FOUND: file {0} could not be opened: {1} * </CODE></PRE> * * To log exception data use {@link #getMessage} or <CODE>printStackTrace</CODE> * family of methods. You should never have to use toString().<P> * * <A NAME="details"> It is also possible to use project- or exception * subhierarchy-specific message resource bundles without maintaining all error * codes in <CODE>com.vladium.exception.exceptions</CODE>. To do so, create a * custom resource bundle and add the following static initializer code to your * base exception class: * <PRE><CODE> * static * { * addExceptionResource (MyException.class, "my_custom_resource_bundle"); * } * </CODE></PRE> * The bundle name is relative to MyException package. This step can omitted if * the bundle name is "exceptions". * * Note that the implementation correctly resolves error code name collisions * across independently developed exception families, as long as resource bundles * use unique names. Specifically, error codes follow inheritance and hiding rules * similar to Java class static methods. See {@link ExceptionCommon#addExceptionResource} * for further details. * * @author Vlad Roubtsov, (C) 2002 */ public abstract class AbstractRuntimeException extends RuntimeException implements ICodedException, IThrowableWrapper { // public: ................................................................ /** * Constructs an exception with null message and null cause. */ public AbstractRuntimeException () { m_cause = null; m_arguments = null; } /** * Constructs an exception with given error message/code and null cause. * * @param message the detail message [can be null] */ public AbstractRuntimeException (final String message) { super (message); m_cause = null; m_arguments = null; } /** * Constructs an exception with given error message/code and null cause. * * @param message the detail message [can be null] * @param arguments message format parameters [can be null or empty] * * @see java.text.MessageFormat */ public AbstractRuntimeException (final String message, final Object [] arguments) { super (message); m_cause = null; m_arguments = arguments == null ? null : (Object []) arguments.clone (); } /** * Constructs an exception with null error message/code and given cause. * * @param cause the cause [nested exception] [can be null] */ public AbstractRuntimeException (final Throwable cause) { super (); m_cause = cause; m_arguments = null; } /** * Constructs an exception with given error message/code and given cause. * * @param message the detail message [can be null] * @param cause the cause [nested exception] [can be null] */ public AbstractRuntimeException (final String message, final Throwable cause) { super (message); m_cause = cause; m_arguments = null; } /** * Constructs an exception with given error message/code and given cause. * * @param message the detail message [can be null] * @param arguments message format parameters [can be null or empty] * @param cause the cause [nested exception] [can be null] * * @see java.text.MessageFormat */ public AbstractRuntimeException (final String message, final Object [] arguments, final Throwable cause) { super (message); m_cause = cause; m_arguments = arguments == null ? null : (Object []) arguments.clone (); } /** * Overrides base method to support error code lookup and avoid returning nulls. * Note that this does not recurse into any 'cause' for message lookup, it only * uses the data passed into the constructor. Subclasses cannot override.<P> * * Equivalent to {@link #getLocalizedMessage}. * * @return String error message provided at construction time or the result * of toString() if no/null message was provided [never null]. */ public final String getMessage () { if (m_message == null) // not synchronized by design { String msg; final String supermsg = super.getMessage (); final Class _class = getClass (); if (m_arguments == null) { msg = ExceptionCommon.getMessage (_class, supermsg); } else { msg = ExceptionCommon.getMessage (_class, supermsg, m_arguments); } if (msg == null) { // this is the same as what's done in Throwable.toString() [copied here to be independent of future JDK changes] final String className = _class.getName (); msg = (supermsg != null) ? (className + ": " + supermsg) : className; } m_message = msg; } return m_message; } /** * Overrides base method for the sole purpose of making it final.<P> * * Equivalent to {@link #getMessage}. */ public final String getLocalizedMessage () { // this is the same as what's done in Throwable // [copied here to be independent of future JDK changes] return getMessage (); } /** * Overrides Exception.printStackTrace() to (a) force the output to go * to System.out and (b) handle nested exceptions in JDKs prior to 1.4.<P> * * Subclasses cannot override. */ public final void printStackTrace () { // NOTE: unlike the JDK implementation, force the output to go to System.out: ExceptionCommon.printStackTrace (this, System.out); } /** * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P> * * Subclasses cannot override. */ public final void printStackTrace (final PrintStream s) { ExceptionCommon.printStackTrace (this, s); } /** * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P> * * Subclasses cannot override. */ public final void printStackTrace (final PrintWriter s) { ExceptionCommon.printStackTrace (this, s); } // ICodedException: /** * Returns the String that was passed as 'message' constructor argument. * Can be null. * * @return message code string [can be null] */ public final String getErrorCode () { return super.getMessage (); } // IThrowableWrapper: /** * This implements {@link IThrowableWrapper} * and also overrides the base method in JDK 1.4+. */ public final Throwable getCause () { return m_cause; } public void __printStackTrace (final PrintStream ps) { super.printStackTrace (ps); } public void __printStackTrace (final PrintWriter pw) { super.printStackTrace (pw); } /** * Equivalent to {@link ExceptionCommon#addExceptionResource}, repeated here for * convenience. Subclasses should invoke from static initializers <I>only</I>. * 'namespace' should be YourException.class. */ public static void addExceptionResource (final Class namespace, final String messageResourceBundleName) { // note: 'namespace' will be the most derived class; it is possible to // auto-detect that in a static method but that requires some security // permissions ExceptionCommon.addExceptionResource (namespace, messageResourceBundleName); } // protected: ............................................................. // package: ............................................................... // private: ............................................................... /* * Ensures that this instance can be serialized even if some message parameters * are not serializable objects. */ private void writeObject (final ObjectOutputStream out) throws IOException { getMessage (); // transform this instance to serializable form out.defaultWriteObject (); } private String m_message; // marshalled/cached result of getMessage() private transient final Object [] m_arguments; // note: this field duplicates functionality available in stock Throwable in JRE 1.4+ private final Throwable m_cause; } // end of class // ----------------------------------------------------------------------------