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: AbstractException.java,v 1.1.1.1 2004/05/09 16:57:57 vlad_r Exp $
8 */
9package com.vladium.util.exception;
10
11import java.io.IOException;
12import java.io.ObjectOutputStream;
13import java.io.PrintStream;
14import java.io.PrintWriter;
15
16// ----------------------------------------------------------------------------
17/**
18 * 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>
19 *
20 * This checked exception class is designed as a base/expansion point for the
21 * entire hierarchy of checked exceptions in a project.<P>
22 *
23 * It provides the following features:
24 * <UL>
25 *    <LI> ability to take in compact error codes that map to full text messages
26 *    in a resource bundle loaded at this class' instantiation time. This avoids
27 *    hardcoding the error messages in product code and allows for easy
28 *    localization of such text if required. Additionally, these messages
29 *    can be parametrized in the java.text.MessageFormat style;
30 *    <LI> exception chaining in J2SE versions prior to 1.4
31 * </UL>
32 *
33 * See {@link AbstractRuntimeException} for an unchecked version of the same class.<P>
34 *
35 * TODO: javadoc
36 *
37 * Each constructor that accepts a String 'message' parameter accepts an error
38 * code as well. You are then responsible for ensuring that either the root
39 * <CODE>com.vladium.exception.exceptions</CODE> resource bundle
40 * or your project/exception class-specific resource bundle [see
41 * <A HREF="#details">below</A> for details] contains a mapping for this error
42 * code. When this lookup fails the passed String value itself will be used as
43 * the error message.<P>
44 *
45 * All constructors taking an 'arguments' parameter supply parameters to the error
46 * message used as a java.text.MessageFormat pattern.<P>
47 *
48 * Example:
49 * <PRE><CODE>
50 *  File file = ...
51 *  try
52 *  ...
53 *  catch (Exception e)
54 *  {
55 *      throw new AbstractException ("FILE_NOT_FOUND", new Object[] {file, e}, e);
56 *  }
57 * </CODE></PRE>
58 *      where <CODE>com.vladium.util.exception.exceptions</CODE> contains:
59 * <PRE><CODE>
60 * FILE_NOT_FOUND: file {0} could not be opened: {1}
61 * </CODE></PRE>
62 *
63 * To log exception data use {@link #getMessage} or <CODE>printStackTrace</CODE>
64 * family of methods. You should never have to use toString().<P>
65 *
66 * <A NAME="details"> It is also possible to use project- or exception
67 * subhierarchy-specific message resource bundles without maintaining all error
68 * codes in <CODE>com.vladium.exception.exceptions</CODE>. To do so, create a
69 * custom resource bundle and add the following static initializer code to your
70 * base exception class:
71 * <PRE><CODE>
72 *  static
73 *  {
74 *      addExceptionResource (MyException.class, "my_custom_resource_bundle");
75 *  }
76 * </CODE></PRE>
77 * The bundle name is relative to MyException package. This step can omitted if
78 * the bundle name is "exceptions".
79 *
80 * Note that the implementation correctly resolves error code name collisions
81 * across independently developed exception families, as long as resource bundles
82 * use unique names. Specifically, error codes follow inheritance and hiding rules
83 * similar to Java class static methods. See {@link ExceptionCommon#addExceptionResource}
84 * for further details.
85 *
86 * @author Vlad Roubtsov, (C) 2002
87 */
88public
89abstract class AbstractException extends Exception implements ICodedException, IThrowableWrapper
90{
91    // public: ................................................................
92
93    /**
94     * Constructs an exception with null message and null cause.
95     */
96    public AbstractException ()
97    {
98        m_cause = null;
99        m_arguments = null;
100    }
101
102    /**
103     * Constructs an exception with given error message/code and null cause.
104     *
105     * @param message the detail message [can be null]
106     */
107    public AbstractException (final String message)
108    {
109        super (message);
110        m_cause = null;
111        m_arguments = null;
112    }
113
114    /**
115     * Constructs an exception with given error message/code and null cause.
116     *
117     * @param message the detail message [can be null]
118     * @param arguments message format parameters [can be null or empty]
119     *
120     * @see java.text.MessageFormat
121     */
122    public AbstractException (final String message, final Object [] arguments)
123    {
124        super (message);
125        m_cause = null;
126        m_arguments = arguments == null ? null : (Object []) arguments.clone ();
127    }
128
129    /**
130     * Constructs an exception with null error message/code and given cause.
131     *
132     * @param cause the cause [nested exception] [can be null]
133     */
134    public AbstractException (final Throwable cause)
135    {
136        super ();
137        m_cause = cause;
138        m_arguments = null;
139    }
140
141    /**
142     * Constructs an exception with given error message/code and given cause.
143     *
144     * @param message the detail message [can be null]
145     * @param cause the cause [nested exception] [can be null]
146     */
147    public AbstractException (final String message, final Throwable cause)
148    {
149        super (message);
150        m_cause = cause;
151        m_arguments = null;
152    }
153
154    /**
155     * Constructs an exception with given error message/code and given cause.
156     *
157     * @param message the detail message [can be null]
158     * @param arguments message format parameters [can be null or empty]
159     * @param cause the cause [nested exception] [can be null]
160     *
161     * @see java.text.MessageFormat
162     */
163    public AbstractException (final String message, final Object [] arguments, final Throwable cause)
164    {
165        super (message);
166        m_cause = cause;
167        m_arguments = arguments == null ? null : (Object []) arguments.clone ();
168    }
169
170
171    /**
172     * Overrides base method to support error code lookup and avoid returning nulls.
173     * Note that this does not recurse into any 'cause' for message lookup, it only
174     * uses the data passed into the constructor. Subclasses cannot override.<P>
175     *
176     * Equivalent to {@link #getLocalizedMessage}.
177     *
178     * @return String error message provided at construction time or the result
179     * of toString() if no/null message was provided [never null].
180     */
181    public final String getMessage ()
182    {
183        if (m_message == null) // not synchronized by design
184        {
185            String msg;
186            final String supermsg = super.getMessage ();
187            final Class _class = getClass ();
188
189            if (m_arguments == null)
190            {
191                msg = ExceptionCommon.getMessage (_class, supermsg);
192            }
193            else
194            {
195                msg = ExceptionCommon.getMessage (_class, supermsg, m_arguments);
196            }
197
198            if (msg == null)
199            {
200                // this is the same as what's done in Throwable.toString() [copied here to be independent of future JDK changes]
201                final String className = _class.getName ();
202
203                msg = (supermsg != null) ? (className + ": " + supermsg) : className;
204            }
205
206            m_message = msg;
207        }
208
209        return m_message;
210    }
211
212    /**
213     * Overrides base method for the sole purpose of making it final.<P>
214     *
215     * Equivalent to {@link #getMessage}.
216     */
217    public final String getLocalizedMessage ()
218    {
219        // this is the same as what's done in Throwable
220        // [copied here to be independent of future JDK changes]
221        return getMessage ();
222    }
223
224    /**
225     * Overrides Exception.printStackTrace() to (a) force the output to go
226     * to System.out and (b) handle nested exceptions in JDKs prior to 1.4.<P>
227     *
228     * Subclasses cannot override.
229     */
230    public final void printStackTrace ()
231    {
232        // NOTE: unlike the JDK implementation, force the output to go to System.out:
233        ExceptionCommon.printStackTrace (this, System.out);
234    }
235
236    /**
237     * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P>
238     *
239     * Subclasses cannot override.
240     */
241    public final void printStackTrace (final PrintStream s)
242    {
243        ExceptionCommon.printStackTrace (this, s);
244    }
245
246    /**
247     * Overrides Exception.printStackTrace() to handle nested exceptions in JDKs prior to 1.4.<P>
248     *
249     * Subclasses cannot override.
250     */
251    public final void printStackTrace (final PrintWriter s)
252    {
253        ExceptionCommon.printStackTrace (this, s);
254    }
255
256    // ICodedException:
257
258    /**
259     * Returns the String that was passed as 'message' constructor argument.
260     * Can be null.
261     *
262     * @return message code string [can be null]
263     */
264    public final String getErrorCode ()
265    {
266        return super.getMessage ();
267    }
268
269    // IThrowableWrapper:
270
271    /**
272     * This implements {@link IThrowableWrapper}
273     * and also overrides the base method in JDK 1.4+.
274     */
275    public final Throwable getCause ()
276    {
277        return m_cause;
278    }
279
280    public void __printStackTrace (final PrintStream ps)
281    {
282        super.printStackTrace (ps);
283    }
284
285    public void __printStackTrace (final PrintWriter pw)
286    {
287        super.printStackTrace (pw);
288    }
289
290    /**
291     * Equivalent to {@link ExceptionCommon#addExceptionResource}, repeated here for
292     * convenience. Subclasses should invoke from static initializers <I>only</I>.
293     * 'namespace' should be YourException.class.
294     */
295    public static void addExceptionResource (final Class namespace,
296                                             final String messageResourceBundleName)
297    {
298        // note: 'namespace' will be the most derived class; it is possible to
299        // auto-detect that in a static method but that requires some security
300        // permissions
301        ExceptionCommon.addExceptionResource (namespace, messageResourceBundleName);
302    }
303
304    // protected: .............................................................
305
306    // package: ...............................................................
307
308    // private: ...............................................................
309
310
311    /*
312     * Ensures that this instance can be serialized even if some message parameters
313     * are not serializable objects.
314     */
315    private void writeObject (final ObjectOutputStream out)
316        throws IOException
317    {
318        getMessage (); // transform this instance to serializable form
319        out.defaultWriteObject ();
320    }
321
322
323    private String m_message; // marshalled/cached result of getMessage()
324    private transient final Object [] m_arguments;
325    // note: this field duplicates functionality available in stock Throwable in JRE 1.4+
326    private final Throwable m_cause;
327
328} // end of class
329// ----------------------------------------------------------------------------
330