001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.lang.exception;
018:
019: import java.io.PrintStream;
020: import java.io.PrintWriter;
021: import java.io.Serializable;
022: import java.io.StringWriter;
023: import java.util.ArrayList;
024: import java.util.Arrays;
025: import java.util.Collections;
026: import java.util.Iterator;
027: import java.util.List;
028:
029: /**
030: * <p>A shared implementation of the nestable exception functionality.</p>
031: * <p>
032: * The code is shared between
033: * {@link org.apache.commons.lang.exception.NestableError NestableError},
034: * {@link org.apache.commons.lang.exception.NestableException NestableException} and
035: * {@link org.apache.commons.lang.exception.NestableRuntimeException NestableRuntimeException}.
036: * </p>
037: *
038: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
039: * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
040: * @author <a href="mailto:knielsen@apache.org">Kasper Nielsen</a>
041: * @author <a href="mailto:steven@caswell.name">Steven Caswell</a>
042: * @author Sean C. Sullivan
043: * @author Stephen Colebourne
044: * @since 1.0
045: * @version $Id: NestableDelegate.java 437554 2006-08-28 06:21:41Z bayard $
046: */
047: public class NestableDelegate implements Serializable {
048:
049: /**
050: * Required for serialization support.
051: *
052: * @see java.io.Serializable
053: */
054: private static final long serialVersionUID = 1L;
055:
056: /**
057: * Constructor error message.
058: */
059: private transient static final String MUST_BE_THROWABLE = "The Nestable implementation passed to the NestableDelegate(Nestable) "
060: + "constructor must extend java.lang.Throwable";
061:
062: /**
063: * Holds the reference to the exception or error that we're
064: * wrapping (which must be a {@link
065: * org.apache.commons.lang.exception.Nestable} implementation).
066: */
067: private Throwable nestable = null;
068:
069: /**
070: * Whether to print the stack trace top-down.
071: * This public flag may be set by calling code, typically in initialisation.
072: * This exists for backwards compatability, setting it to false will return
073: * the library to v1.0 behaviour (but will affect all users of the library
074: * in the classloader).
075: * @since 2.0
076: */
077: public static boolean topDown = true;
078:
079: /**
080: * Whether to trim the repeated stack trace.
081: * This public flag may be set by calling code, typically in initialisation.
082: * This exists for backwards compatability, setting it to false will return
083: * the library to v1.0 behaviour (but will affect all users of the library
084: * in the classloader).
085: * @since 2.0
086: */
087: public static boolean trimStackFrames = true;
088:
089: /**
090: * Whether to match subclasses via indexOf.
091: * This public flag may be set by calling code, typically in initialisation.
092: * This exists for backwards compatability, setting it to false will return
093: * the library to v2.0 behaviour (but will affect all users of the library
094: * in the classloader).
095: * @since 2.1
096: */
097: public static boolean matchSubclasses = true;
098:
099: /**
100: * Constructs a new <code>NestableDelegate</code> instance to manage the
101: * specified <code>Nestable</code>.
102: *
103: * @param nestable the Nestable implementation (<i>must</i> extend
104: * {@link java.lang.Throwable})
105: * @since 2.0
106: */
107: public NestableDelegate(Nestable nestable) {
108: if (nestable instanceof Throwable) {
109: this .nestable = (Throwable) nestable;
110: } else {
111: throw new IllegalArgumentException(MUST_BE_THROWABLE);
112: }
113: }
114:
115: /**
116: * Returns the error message of the <code>Throwable</code> in the chain of <code>Throwable</code>s at the
117: * specified index, numbered from 0.
118: *
119: * @param index
120: * the index of the <code>Throwable</code> in the chain of <code>Throwable</code>s
121: * @return the error message, or null if the <code>Throwable</code> at the specified index in the chain does not
122: * contain a message
123: * @throws IndexOutOfBoundsException
124: * if the <code>index</code> argument is negative or not less than the count of <code>Throwable</code>s
125: * in the chain
126: * @since 2.0
127: */
128: public String getMessage(int index) {
129: Throwable t = this .getThrowable(index);
130: if (Nestable.class.isInstance(t)) {
131: return ((Nestable) t).getMessage(0);
132: }
133: return t.getMessage();
134: }
135:
136: /**
137: * Returns the full message contained by the <code>Nestable</code> and any nested <code>Throwable</code>s.
138: *
139: * @param baseMsg
140: * the base message to use when creating the full message. Should be generally be called via
141: * <code>nestableHelper.getMessage(super.getMessage())</code>, where <code>super</code> is an
142: * instance of {@link java.lang.Throwable}.
143: * @return The concatenated message for this and all nested <code>Throwable</code>s
144: * @since 2.0
145: */
146: public String getMessage(String baseMsg) {
147: Throwable nestedCause = ExceptionUtils.getCause(this .nestable);
148: String causeMsg = nestedCause == null ? null : nestedCause
149: .getMessage();
150: if (nestedCause == null || causeMsg == null) {
151: return baseMsg; // may be null, which is a valid result
152: }
153: if (baseMsg == null) {
154: return causeMsg;
155: }
156: return baseMsg + ": " + causeMsg;
157: }
158:
159: /**
160: * Returns the error message of this and any nested <code>Throwable</code>s in an array of Strings, one element
161: * for each message. Any <code>Throwable</code> not containing a message is represented in the array by a null.
162: * This has the effect of cause the length of the returned array to be equal to the result of the
163: * {@link #getThrowableCount()} operation.
164: *
165: * @return the error messages
166: * @since 2.0
167: */
168: public String[] getMessages() {
169: Throwable[] throwables = this .getThrowables();
170: String[] msgs = new String[throwables.length];
171: for (int i = 0; i < throwables.length; i++) {
172: msgs[i] = (Nestable.class.isInstance(throwables[i]) ? ((Nestable) throwables[i])
173: .getMessage(0)
174: : throwables[i].getMessage());
175: }
176: return msgs;
177: }
178:
179: /**
180: * Returns the <code>Throwable</code> in the chain of
181: * <code>Throwable</code>s at the specified index, numbered from 0.
182: *
183: * @param index the index, numbered from 0, of the <code>Throwable</code> in
184: * the chain of <code>Throwable</code>s
185: * @return the <code>Throwable</code>
186: * @throws IndexOutOfBoundsException if the <code>index</code> argument is
187: * negative or not less than the count of <code>Throwable</code>s in the
188: * chain
189: * @since 2.0
190: */
191: public Throwable getThrowable(int index) {
192: if (index == 0) {
193: return this .nestable;
194: }
195: Throwable[] throwables = this .getThrowables();
196: return throwables[index];
197: }
198:
199: /**
200: * Returns the number of <code>Throwable</code>s contained in the
201: * <code>Nestable</code> contained by this delegate.
202: *
203: * @return the throwable count
204: * @since 2.0
205: */
206: public int getThrowableCount() {
207: return ExceptionUtils.getThrowableCount(this .nestable);
208: }
209:
210: /**
211: * Returns this delegate's <code>Nestable</code> and any nested
212: * <code>Throwable</code>s in an array of <code>Throwable</code>s, one
213: * element for each <code>Throwable</code>.
214: *
215: * @return the <code>Throwable</code>s
216: * @since 2.0
217: */
218: public Throwable[] getThrowables() {
219: return ExceptionUtils.getThrowables(this .nestable);
220: }
221:
222: /**
223: * Returns the index, numbered from 0, of the first <code>Throwable</code>
224: * that matches the specified type, or a subclass, in the chain of <code>Throwable</code>s
225: * with an index greater than or equal to the specified index.
226: * The method returns -1 if the specified type is not found in the chain.
227: * <p>
228: * NOTE: From v2.1, we have clarified the <code>Nestable</code> interface
229: * such that this method matches subclasses.
230: * If you want to NOT match subclasses, please use
231: * {@link ExceptionUtils#indexOfThrowable(Throwable, Class, int)}
232: * (which is avaiable in all versions of lang).
233: * An alternative is to use the public static flag {@link #matchSubclasses}
234: * on <code>NestableDelegate</code>, however this is not recommended.
235: *
236: * @param type the type to find, subclasses match, null returns -1
237: * @param fromIndex the index, numbered from 0, of the starting position in
238: * the chain to be searched
239: * @return index of the first occurrence of the type in the chain, or -1 if
240: * the type is not found
241: * @throws IndexOutOfBoundsException if the <code>fromIndex</code> argument
242: * is negative or not less than the count of <code>Throwable</code>s in the
243: * chain
244: * @since 2.0
245: */
246: public int indexOfThrowable(Class type, int fromIndex) {
247: if (type == null) {
248: return -1;
249: }
250: if (fromIndex < 0) {
251: throw new IndexOutOfBoundsException(
252: "The start index was out of bounds: " + fromIndex);
253: }
254: Throwable[] throwables = ExceptionUtils
255: .getThrowables(this .nestable);
256: if (fromIndex >= throwables.length) {
257: throw new IndexOutOfBoundsException(
258: "The start index was out of bounds: " + fromIndex
259: + " >= " + throwables.length);
260: }
261: if (matchSubclasses) {
262: for (int i = fromIndex; i < throwables.length; i++) {
263: if (type.isAssignableFrom(throwables[i].getClass())) {
264: return i;
265: }
266: }
267: } else {
268: for (int i = fromIndex; i < throwables.length; i++) {
269: if (type.equals(throwables[i].getClass())) {
270: return i;
271: }
272: }
273: }
274: return -1;
275: }
276:
277: /**
278: * Prints the stack trace of this exception the the standar error
279: * stream.
280: */
281: public void printStackTrace() {
282: printStackTrace(System.err);
283: }
284:
285: /**
286: * Prints the stack trace of this exception to the specified
287: * stream.
288: *
289: * @param out <code>PrintStream</code> to use for output.
290: * @see #printStackTrace(PrintWriter)
291: */
292: public void printStackTrace(PrintStream out) {
293: synchronized (out) {
294: PrintWriter pw = new PrintWriter(out, false);
295: printStackTrace(pw);
296: // Flush the PrintWriter before it's GC'ed.
297: pw.flush();
298: }
299: }
300:
301: /**
302: * Prints the stack trace of this exception to the specified
303: * writer. If the Throwable class has a <code>getCause</code>
304: * method (i.e. running on jre1.4 or higher), this method just
305: * uses Throwable's printStackTrace() method. Otherwise, generates
306: * the stack-trace, by taking into account the 'topDown' and
307: * 'trimStackFrames' parameters. The topDown and trimStackFrames
308: * are set to 'true' by default (produces jre1.4-like stack trace).
309: *
310: * @param out <code>PrintWriter</code> to use for output.
311: */
312: public void printStackTrace(PrintWriter out) {
313: Throwable throwable = this .nestable;
314: // if running on jre1.4 or higher, use default printStackTrace
315: if (ExceptionUtils.isThrowableNested()) {
316: if (throwable instanceof Nestable) {
317: ((Nestable) throwable).printPartialStackTrace(out);
318: } else {
319: throwable.printStackTrace(out);
320: }
321: return;
322: }
323:
324: // generating the nested stack trace
325: List stacks = new ArrayList();
326: while (throwable != null) {
327: String[] st = getStackFrames(throwable);
328: stacks.add(st);
329: throwable = ExceptionUtils.getCause(throwable);
330: }
331:
332: // If NOT topDown, reverse the stack
333: String separatorLine = "Caused by: ";
334: if (!topDown) {
335: separatorLine = "Rethrown as: ";
336: Collections.reverse(stacks);
337: }
338:
339: // Remove the repeated lines in the stack
340: if (trimStackFrames) {
341: trimStackFrames(stacks);
342: }
343:
344: synchronized (out) {
345: for (Iterator iter = stacks.iterator(); iter.hasNext();) {
346: String[] st = (String[]) iter.next();
347: for (int i = 0, len = st.length; i < len; i++) {
348: out.println(st[i]);
349: }
350: if (iter.hasNext()) {
351: out.print(separatorLine);
352: }
353: }
354: }
355: }
356:
357: /**
358: * Captures the stack trace associated with the specified
359: * <code>Throwable</code> object, decomposing it into a list of
360: * stack frames.
361: *
362: * @param t The <code>Throwable</code>.
363: * @return An array of strings describing each stack frame.
364: * @since 2.0
365: */
366: protected String[] getStackFrames(Throwable t) {
367: StringWriter sw = new StringWriter();
368: PrintWriter pw = new PrintWriter(sw, true);
369:
370: // Avoid infinite loop between decompose() and printStackTrace().
371: if (t instanceof Nestable) {
372: ((Nestable) t).printPartialStackTrace(pw);
373: } else {
374: t.printStackTrace(pw);
375: }
376: return ExceptionUtils.getStackFrames(sw.getBuffer().toString());
377: }
378:
379: /**
380: * Trims the stack frames. The first set is left untouched. The rest
381: * of the frames are truncated from the bottom by comparing with
382: * one just on top.
383: *
384: * @param stacks The list containing String[] elements
385: * @since 2.0
386: */
387: protected void trimStackFrames(List stacks) {
388: for (int size = stacks.size(), i = size - 1; i > 0; i--) {
389: String[] curr = (String[]) stacks.get(i);
390: String[] next = (String[]) stacks.get(i - 1);
391:
392: List currList = new ArrayList(Arrays.asList(curr));
393: List nextList = new ArrayList(Arrays.asList(next));
394: ExceptionUtils.removeCommonFrames(currList, nextList);
395:
396: int trimmed = curr.length - currList.size();
397: if (trimmed > 0) {
398: currList.add("\t... " + trimmed + " more");
399: stacks.set(i, currList.toArray(new String[currList
400: .size()]));
401: }
402: }
403: }
404: }
|