001: /*
002: * @(#)ChainableExceptionHelper.java
003: *
004: * Copyright (C) 2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Part of the GroboUtils package at:
009: * http://groboutils.sourceforge.net
010: *
011: * Permission is hereby granted, free of charge, to any person obtaining a
012: * copy of this software and associated documentation files (the "Software"),
013: * to deal in the Software without restriction, including without limitation
014: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
015: * and/or sell copies of the Software, and to permit persons to whom the
016: * Software is furnished to do so, subject to the following conditions:
017: *
018: * The above copyright notice and this permission notice shall be included in
019: * all copies or substantial portions of the Software.
020: *
021: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
022: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
023: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
024: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
025: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
026: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
027: * DEALINGS IN THE SOFTWARE.
028: */
029: package net.sourceforge.groboutils.util.throwable.v1;
030:
031: import java.io.PrintStream;
032: import java.io.PrintWriter;
033: import java.io.Serializable;
034:
035: /**
036: * Helper class to support easy-to-implement chainable exceptions. In
037: * most situations, it is close to impossible to create a generic chainable
038: * exception due to inheritance restrictions on the Java exception hierarchy
039: * (this shows a Java weakness - exception categories should be interfaces,
040: * not specific classes).
041: * <P>
042: * This will attempt to use the owning source's <tt>initCause()</tt> and
043: * <tt>getCause()</tt> methods, provided the owning source provides those
044: * methods. Only the superclass's implementation that provides these methods
045: * yet does not implement <tt>IChainableException</tt> will be used
046: * (to prevent a possible recursion nightmare).
047: * <P>
048: * In order to prevent endless recursion, this class will not look at the
049: * JDK 1.4 implementation of the source exception. This used to work with
050: * JDK 1.4.0, but JDK 1.4.2 seems to have broken the original implementation.
051: *
052: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
053: * @version $Date: 2003/05/06 05:35:01 $
054: * @since July 7, 2002
055: */
056: public class ChainableExceptionHelper implements Serializable {
057: private Throwable source;
058:
059: /**
060: * The chained-to cause for this exception. Note that if the source cause
061: * is set and allows the cause to be set, then this cause will be null.
062: * If the source cannot store the cause, then this cause will be set to
063: * the correct cause, which may be <tt>null</tt>.
064: */
065: private Throwable cause;
066:
067: /**
068: * Flag to indicate if the cause was ever set. Needed, since the set cause
069: * may be <tt>null</tt>.
070: */
071: private boolean causeSet = false;
072:
073: /**
074: * Sets the owning throwable. The initCause() method can still be
075: * called after this constructor is used.
076: */
077: public ChainableExceptionHelper(Throwable source) {
078: if (source == null) {
079: throw new IllegalArgumentException("no null arguments");
080: }
081: this .source = source;
082: }
083:
084: public ChainableExceptionHelper(Throwable source, Throwable cause) {
085: this (source);
086: initCause(cause);
087: }
088:
089: /**
090: * JDK 1.4 compatible method.
091: * <P>
092: * <i>from the JDK 1.4 documentation:</i>
093: * <BLOCKQUOTE>
094: * Returns the cause of this throwable or <tt>null</tt> if the cause is
095: * nonexistent or unknown. (The cause is the throwable that caused this
096: * throwable to get thrown.)
097: * <P>
098: * This implementation returns the cause that was supplied via one of the
099: * constructors requiring a <tt>Throwable</tt>, or that was set after
100: * creation with the <tt>initCause( Throwable )</tt> method. While it is
101: * typically unnecessary to override this method, a subclass can override
102: * it to return a cause set by some other means. This is appropriate for a
103: * "legacy chained throwable" that predates the addition of chained
104: * exceptions to <tt>Throwable</tt>. Note that it is not necessary to
105: * override any of the <tt>PrintStackTrace</tt> methods, all of which
106: * invoke the getCause method to determine the cause of a throwable.
107: * </BLOCKQUOTE>
108: *
109: * @return the cause of this throwable or <tt>null</tt> if the cause is
110: * nonexistent or unknown.
111: */
112: public Throwable getCause() {
113: Throwable t = null;
114: if (this .causeSet) {
115: // may still be null
116: t = this .cause;
117: }
118: return t;
119: }
120:
121: /**
122: * JDK 1.4 compatible method.
123: * <P>
124: * <i>from the JDK 1.4 documentation:</i>
125: * <BLOCKQUOTE>
126: * Initializes the cause of this throwable to the specified value.
127: * (The cause is the throwable that caused this throwable to get thrown.)
128: * <P>
129: * This method can be called at most once. It is generally called from
130: * within the constructor, or immediately after creating the throwable.
131: * If this throwable was created with Throwable(Throwable) or
132: * Throwable(String,Throwable), this method cannot be called even once.
133: * </BLOCKQUOTE>
134: *
135: * @param cause the cause (which is saved for later retrieval by the
136: * getCause() method). (A null value is permitted, and indicates
137: * that the cause is nonexistent or unknown.)
138: * @param source the exception that will be the underlying exception
139: * @return a reference to this Throwable instance.
140: * @exception IllegalArgumentException if cause is this throwable.
141: * (A throwable cannot be its own cause.)
142: * @exception IllegalStateException if this throwable was created with
143: * Throwable(Throwable) or Throwable(String,Throwable), or this
144: * method has already been called on this throwable.
145: */
146: public synchronized Throwable initCause(Throwable cause) {
147: if (this .causeSet) {
148: throw new IllegalStateException("Already set cause");
149: }
150: if (cause == this .source) {
151: throw new IllegalArgumentException(
152: "exception cannot cause itself.");
153: }
154:
155: this .causeSet = true;
156: this .cause = cause;
157: return this .source;
158: }
159:
160: /**
161: * For non-JDK 1.4 compatible VMs, this overrides the original behavior
162: * to describe the underlying cause. Special logic is performed to ensure
163: * that no JDK 1.4 VM is being used when the inner exception is displayed
164: * (in order to prevent double printing).
165: */
166: public void printStackTrace(PrintStream ps) {
167: this .source.printStackTrace(ps);
168: if (shouldDisplayCause()) {
169: ps.println(getUnderlyingExceptionSeparator());
170: Throwable t = getCause();
171: if (t == null) {
172: ps.println(getUnknownExceptionString());
173: } else {
174: t.printStackTrace(ps);
175: }
176: }
177: }
178:
179: /**
180: * For non-JDK 1.4 compatible VMs, this overrides the original behavior
181: * to describe the underlying cause. Special logic is performed to ensure
182: * that no JDK 1.4 VM is being used when the inner exception is displayed
183: * (in order to prevent double printing).
184: */
185: public void printStackTrace(PrintWriter pw) {
186: this .source.printStackTrace(pw);
187: if (shouldDisplayCause()) {
188: pw.println(getUnderlyingExceptionSeparator());
189: Throwable t = getCause();
190: if (t == null) {
191: pw.println(getUnknownExceptionString());
192: } else {
193: t.printStackTrace(pw);
194: }
195: }
196: }
197:
198: protected String getUnderlyingExceptionSeparator() {
199: return "-------- Underlying exception --------";
200: }
201:
202: protected String getUnknownExceptionString() {
203: return "Unknown or non-existent exception";
204: }
205:
206: protected boolean shouldDisplayCause() {
207: // if the cause was never set, then don't display the cause.
208: // if it was set (even if null), then we should display it.
209: if (!this .causeSet) {
210: return false;
211: }
212:
213: // we need to do it.
214: return true;
215: }
216: }
|