1 package org.codehaus.plexus.util;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution, if
22 * any, must include the following acknowledgement:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.codehaus.org/)."
25 * Alternately, this acknowledgement may appear in the software itself,
26 * if and wherever such third-party acknowledgements normally appear.
27 *
28 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
29 * Foundation" must not be used to endorse or promote products derived
30 * from this software without prior written permission. For written
31 * permission, please contact codehaus@codehaus.org.
32 *
33 * 5. Products derived from this software may not be called "Apache"
34 * nor may "Apache" appear in their names without prior written
35 * permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.codehaus.org/>.
55 */
56
57 import java.io.PrintStream;
58 import java.io.PrintWriter;
59 import java.io.StringWriter;
60 import java.lang.reflect.Field;
61 import java.lang.reflect.InvocationTargetException;
62 import java.lang.reflect.Method;
63 import java.sql.SQLException;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.StringTokenizer;
69
70 /**
71 * <p>
72 * <code>ExceptionUtils</code> provides utilities for manipulating <code>Throwable</code> objects.
73 * </p>
74 *
75 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
76 * @author Dmitri Plotnikov
77 * @author Stephen Colebourne
78 * @since 1.0
79 *
80 */
81 public class ExceptionUtils
82 {
83 /**
84 * Used when printing stack frames to denote the start of a wrapped exception. Package private for accessibility by
85 * test suite.
86 */
87 static final String WRAPPED_MARKER = " [wrapped] ";
88
89 /**
90 * The names of methods commonly used to access a wrapped exception.
91 */
92 protected static String[] CAUSE_METHOD_NAMES = { "getCause", "getNextException", "getTargetException",
93 "getException", "getSourceException", "getRootCause", "getCausedByException", "getNested" };
94
95 /**
96 * Constructs a new <code>ExceptionUtils</code>. Protected to discourage instantiation.
97 */
98 protected ExceptionUtils()
99 {
100 }
101
102 /**
103 * <p>
104 * Adds to the list of method names used in the search for <code>Throwable</code> objects.
105 * </p>
106 *
107 * @param methodName the methodName to add to the list, null and empty strings are ignored
108 */
109 public static void addCauseMethodName( String methodName )
110 {
111 if ( methodName != null && methodName.length() > 0 )
112 {
113 List<String> list = new ArrayList<String>( Arrays.asList( CAUSE_METHOD_NAMES ) );
114 list.add( methodName );
115 CAUSE_METHOD_NAMES = list.toArray( new String[0] );
116 }
117 }
118
119 /**
120 * <p>
121 * Introspects the specified <code>Throwable</code> to obtain the cause.
122 * </p>
123 * <p>
124 * The method searches for methods with specific names that return a <code>Throwable</code> object. This will pick
125 * up most wrapping exceptions, including those from JDK 1.4, and The method names can be added to using
126 * {@link #addCauseMethodName(String)}. The default list searched for are:
127 * </p>
128 * <ul>
129 * <li><code>getCause()</code>
130 * <li><code>getNextException()</code>
131 * <li><code>getTargetException()</code>
132 * <li><code>getException()</code>
133 * <li><code>getSourceException()</code>
134 * <li><code>getRootCause()</code>
135 * <li><code>getCausedByException()</code>
136 * <li><code>getNested()</code>
137 * </ul>
138 * <p>
139 * In the absence of any such method, the object is inspected for a <code>detail</code> field assignable to a
140 * <code>Throwable</code>.
141 * </p>
142 * <p>
143 * If none of the above is found, returns <code>null</code>.
144 * </p>
145 *
146 * @param throwable The exception to introspect for a cause.
147 * @return The cause of the <code>Throwable</code>.
148 * @throws NullPointerException if the throwable is null
149 */
150 public static Throwable getCause( Throwable throwable )
151 {
152 return getCause( throwable, CAUSE_METHOD_NAMES );
153 }
154
155 /**
156 * <p>
157 * Introspects the specified <code>Throwable</code> to obtain the cause using a supplied array of method names.
158 * </p>
159 *
160 * @param throwable The exception to introspect for a cause.
161 * @param methodNames the methods names to match
162 * @return The cause of the <code>Throwable</code>.
163 * @throws NullPointerException if the method names array is null or contains null
164 * @throws NullPointerException if the throwable is null
165 */
166 public static Throwable getCause( Throwable throwable, String[] methodNames )
167 {
168 Throwable cause = getCauseUsingWellKnownTypes( throwable );
169 if ( cause == null )
170 {
171 for ( String methodName : methodNames )
172 {
173 cause = getCauseUsingMethodName( throwable, methodName );
174 if ( cause != null )
175 {
176 break;
177 }
178 }
179
180 if ( cause == null )
181 {
182 cause = getCauseUsingFieldName( throwable, "detail" );
183 }
184 }
185 return cause;
186 }
187
188 /**
189 * <p>
190 * Walks through the exception chain to the last element -- the "root" of the tree -- using
191 * {@link #getCause(Throwable)}, and returns that exception.
192 * </p>
193 *
194 * @param throwable the throwable to get the root cause for
195 * @return The root cause of the <code>Throwable</code>.
196 */
197 public static Throwable getRootCause( Throwable throwable )
198 {
199 Throwable cause = getCause( throwable );
200 if ( cause != null )
201 {
202 throwable = cause;
203 while ( ( throwable = getCause( throwable ) ) != null )
204 {
205 cause = throwable;
206 }
207 }
208 return cause;
209 }
210
211 /**
212 * <p>
213 * Uses <code>instanceof</code> checks to examine the exception, looking for well known types which could contain
214 * chained or wrapped exceptions.
215 * </p>
216 *
217 * @param throwable the exception to examine
218 * @return The wrapped exception, or <code>null</code> if not found.
219 */
220 private static Throwable getCauseUsingWellKnownTypes( Throwable throwable )
221 {
222 if ( throwable instanceof SQLException )
223 {
224 return ( (SQLException) throwable ).getNextException();
225 }
226 else if ( throwable instanceof InvocationTargetException )
227 {
228 return ( (InvocationTargetException) throwable ).getTargetException();
229 }
230 else
231 {
232 return null;
233 }
234 }
235
236 /**
237 * <p>
238 * Find a throwable by method name.
239 * </p>
240 *
241 * @param throwable the exception to examine
242 * @param methodName the name of the method to find and invoke
243 * @return The wrapped exception, or <code>null</code> if not found.
244 */
245 private static Throwable getCauseUsingMethodName( Throwable throwable, String methodName )
246 {
247 Method method = null;
248 try
249 {
250 method = throwable.getClass().getMethod( methodName, null );
251 }
252 catch ( NoSuchMethodException ignored )
253 {
254 }
255 catch ( SecurityException ignored )
256 {
257 }
258
259 if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) )
260 {
261 try
262 {
263 return (Throwable) method.invoke( throwable, new Object[0] );
264 }
265 catch ( IllegalAccessException ignored )
266 {
267 }
268 catch ( IllegalArgumentException ignored )
269 {
270 }
271 catch ( InvocationTargetException ignored )
272 {
273 }
274 }
275 return null;
276 }
277
278 /**
279 * <p>
280 * Find a throwable by field name.
281 * </p>
282 *
283 * @param throwable the exception to examine
284 * @param fieldName the name of the attribute to examine
285 * @return The wrapped exception, or <code>null</code> if not found.
286 */
287 private static Throwable getCauseUsingFieldName( Throwable throwable, String fieldName )
288 {
289 Field field = null;
290 try
291 {
292 field = throwable.getClass().getField( fieldName );
293 }
294 catch ( NoSuchFieldException ignored )
295 {
296 }
297 catch ( SecurityException ignored )
298 {
299 }
300
301 if ( field != null && Throwable.class.isAssignableFrom( field.getType() ) )
302 {
303 try
304 {
305 return (Throwable) field.get( throwable );
306 }
307 catch ( IllegalAccessException ignored )
308 {
309 }
310 catch ( IllegalArgumentException ignored )
311 {
312 }
313 }
314 return null;
315 }
316
317 /**
318 * <p>
319 * Returns the number of <code>Throwable</code> objects in the exception chain.
320 * </p>
321 *
322 * @param throwable the exception to inspect
323 * @return The throwable count.
324 */
325 public static int getThrowableCount( Throwable throwable )
326 {
327 // Count the number of throwables
328 int count = 0;
329 while ( throwable != null )
330 {
331 count++;
332 throwable = ExceptionUtils.getCause( throwable );
333 }
334 return count;
335 }
336
337 /**
338 * <p>
339 * Returns the list of <code>Throwable</code> objects in the exception chain.
340 * </p>
341 *
342 * @param throwable the exception to inspect
343 * @return The list of <code>Throwable</code> objects.
344 */
345 public static Throwable[] getThrowables( Throwable throwable )
346 {
347 List<Throwable> list = new ArrayList<>();
348 while ( throwable != null )
349 {
350 list.add( throwable );
351 throwable = getCause( throwable );
352 }
353 return list.toArray( new Throwable[0] );
354 }
355
356 /**
357 * <p>
358 * Delegates to {@link #indexOfThrowable(Throwable, Class, int)}, starting the search at the beginning of the
359 * exception chain.
360 * </p>
361 * @param throwable the exception to inspect
362 * @param type <code>Class</code> to look for
363 * @return index of the stack matching the type
364 * @see #indexOfThrowable(Throwable, Class, int)
365 */
366 public static int indexOfThrowable( Throwable throwable, Class type )
367 {
368 return indexOfThrowable( throwable, type, 0 );
369 }
370
371 /**
372 * <p>
373 * Returns the (zero based) index, of the first <code>Throwable</code> that matches the specified type in the
374 * exception chain of <code>Throwable</code> objects with an index greater than or equal to the specified index, or
375 * <code>-1</code> if the type is not found.
376 * </p>
377 *
378 * @param throwable the exception to inspect
379 * @param type <code>Class</code> to look for
380 * @param fromIndex the (zero based) index of the starting position in the chain to be searched
381 * @return the first occurrence of the type in the chain, or <code>-1</code> if the type is not found
382 * @throws IndexOutOfBoundsException If the <code>fromIndex</code> argument is negative or not less than the count
383 * of <code>Throwable</code>s in the chain.
384 */
385 public static int indexOfThrowable( Throwable throwable, Class type, int fromIndex )
386 {
387 if ( fromIndex < 0 )
388 {
389 throw new IndexOutOfBoundsException( "Throwable index out of range: " + fromIndex );
390 }
391 Throwable[] throwables = ExceptionUtils.getThrowables( throwable );
392 if ( fromIndex >= throwables.length )
393 {
394 throw new IndexOutOfBoundsException( "Throwable index out of range: " + fromIndex );
395 }
396 for ( int i = fromIndex; i < throwables.length; i++ )
397 {
398 if ( throwables[i].getClass().equals( type ) )
399 {
400 return i;
401 }
402 }
403 return -1;
404 }
405
406 /**
407 * Prints a compact stack trace for the root cause of a throwable. The compact stack trace starts with the root
408 * cause and prints stack frames up to the place where it was caught and wrapped. Then it prints the wrapped
409 * exception and continues with stack frames until the wrapper exception is caught and wrapped again, etc.
410 * <p>
411 * The method is equivalent to t.printStackTrace() for throwables that don't have nested causes.
412 * @param t the exception
413 * @param stream the stream
414 */
415 public static void printRootCauseStackTrace( Throwable t, PrintStream stream )
416 {
417 String trace[] = getRootCauseStackTrace( t );
418 for ( String aTrace : trace )
419 {
420 stream.println( aTrace );
421 }
422 stream.flush();
423 }
424
425 /**
426 * Equivalent to printRootCauseStackTrace(t, System.err)
427 * @param t the exception
428 */
429 public static void printRootCauseStackTrace( Throwable t )
430 {
431 printRootCauseStackTrace( t, System.err );
432 }
433
434 /**
435 * Same as printRootCauseStackTrace(t, stream), except it takes a PrintWriter as an argument.
436 * @param t the cause
437 * @param writer the writer
438 */
439 public static void printRootCauseStackTrace( Throwable t, PrintWriter writer )
440 {
441 String trace[] = getRootCauseStackTrace( t );
442 for ( String aTrace : trace )
443 {
444 writer.println( aTrace );
445 }
446 writer.flush();
447 }
448
449 /**
450 * Creates a compact stack trace for the root cause of the supplied throwable. See
451 * <code>printRootCauseStackTrace(Throwable t, PrintStream s)</code>
452 * @param t the cause
453 * @return the Stack
454 */
455 public static String[] getRootCauseStackTrace( Throwable t )
456 {
457 Throwable[] throwables = getThrowables( t );
458 int count = throwables.length;
459 ArrayList<String> frames = new ArrayList<>();
460 List<String> nextTrace = getStackFrameList( throwables[count - 1] );
461 for ( int i = count; --i >= 0; )
462 {
463 List<String> trace = nextTrace;
464 if ( i != 0 )
465 {
466 nextTrace = getStackFrameList( throwables[i - 1] );
467 removeCommonFrames( trace, nextTrace );
468 }
469 if ( i == ( count - 1 ) )
470 {
471 frames.add( throwables[i].toString() );
472 }
473 else
474 {
475 frames.add( WRAPPED_MARKER + throwables[i].toString() );
476 }
477 for ( String aTrace : trace )
478 {
479 frames.add( aTrace );
480 }
481 }
482 return frames.toArray( new String[0] );
483 }
484
485 /**
486 * Given two stack traces, removes common frames from the cause trace.
487 *
488 * @param causeFrames stack trace of a cause throwable
489 * @param wrapperFrames stack trace of a wrapper throwable
490 */
491 private static void removeCommonFrames( List<String> causeFrames, List<String> wrapperFrames )
492 {
493 int causeFrameIndex = causeFrames.size() - 1;
494 int wrapperFrameIndex = wrapperFrames.size() - 1;
495 while ( causeFrameIndex >= 0 && wrapperFrameIndex >= 0 )
496 {
497 // Remove the frame from the cause trace if it is the same
498 // as in the wrapper trace
499 String causeFrame = causeFrames.get( causeFrameIndex );
500 String wrapperFrame = wrapperFrames.get( wrapperFrameIndex );
501 if ( causeFrame.equals( wrapperFrame ) )
502 {
503 causeFrames.remove( causeFrameIndex );
504 }
505 causeFrameIndex--;
506 wrapperFrameIndex--;
507 }
508 }
509
510 /**
511 * A convenient way of extracting the stack trace from an exception.
512 *
513 * @param t The <code>Throwable</code>.
514 * @return The stack trace as generated by the exception's <code>printStackTrace(PrintWriter)</code> method.
515 */
516 public static String getStackTrace( Throwable t )
517 {
518 StringWriter sw = new StringWriter();
519 PrintWriter pw = new PrintWriter( sw, true );
520 t.printStackTrace( pw );
521 return sw.getBuffer().toString();
522 }
523
524 /**
525 * A way to get the entire nested stack-trace of an throwable.
526 *
527 * @param t The <code>Throwable</code>.
528 * @return The nested stack trace, with the root cause first.
529 */
530 public static String getFullStackTrace( Throwable t )
531 {
532 StringWriter sw = new StringWriter();
533 PrintWriter pw = new PrintWriter( sw, true );
534 Throwable[] ts = getThrowables( t );
535 for ( Throwable t1 : ts )
536 {
537 t1.printStackTrace( pw );
538 if ( isNestedThrowable( t1 ) )
539 {
540 break;
541 }
542 }
543 return sw.getBuffer().toString();
544 }
545
546 /**
547 * Whether an Throwable is considered nested or not.
548 *
549 * @param throwable The <code>Throwable</code>.
550 * @return boolean true/false
551 */
552 public static boolean isNestedThrowable( Throwable throwable )
553 {
554 if ( throwable == null )
555 {
556 return false;
557 }
558
559 if ( throwable instanceof SQLException )
560 {
561 return true;
562 }
563 else if ( throwable instanceof InvocationTargetException )
564 {
565 return true;
566 }
567
568 for ( String CAUSE_METHOD_NAME : CAUSE_METHOD_NAMES )
569 {
570 try
571 {
572 Method method = throwable.getClass().getMethod( CAUSE_METHOD_NAME, null );
573 if ( method != null )
574 {
575 return true;
576 }
577 }
578 catch ( NoSuchMethodException ignored )
579 {
580 }
581 catch ( SecurityException ignored )
582 {
583 }
584 }
585
586 try
587 {
588 Field field = throwable.getClass().getField( "detail" );
589 if ( field != null )
590 {
591 return true;
592 }
593 }
594 catch ( NoSuchFieldException ignored )
595 {
596 }
597 catch ( SecurityException ignored )
598 {
599 }
600
601 return false;
602 }
603
604 /**
605 * Captures the stack trace associated with the specified <code>Throwable</code> object, decomposing it into a list
606 * of stack frames.
607 *
608 * @param t The <code>Throwable</code>.
609 * @return An array of strings describing each stack frame.
610 */
611 public static String[] getStackFrames( Throwable t )
612 {
613 return getStackFrames( getStackTrace( t ) );
614 }
615
616 /**
617 * Functionality shared between the <code>getStackFrames(Throwable)</code> methods of this and the classes.
618 */
619 static String[] getStackFrames( String stackTrace )
620 {
621 String linebreak = System.getProperty( "line.separator" );
622 StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
623 List<String> list = new LinkedList<String>();
624 while ( frames.hasMoreTokens() )
625 {
626 list.add( frames.nextToken() );
627 }
628 return list.toArray( new String[0] );
629 }
630
631 /**
632 * Produces a List of stack frames - the message is not included. This works in most cases - it will only fail if
633 * the exception message contains a line that starts with: " at".
634 *
635 * @param t is any throwable
636 * @return List of stack frames
637 */
638 static List<String> getStackFrameList( Throwable t )
639 {
640 String stackTrace = getStackTrace( t );
641 String linebreak = System.getProperty( "line.separator" );
642 StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
643 List<String> list = new LinkedList<String>();
644 boolean traceStarted = false;
645 while ( frames.hasMoreTokens() )
646 {
647 String token = frames.nextToken();
648 // Determine if the line starts with <whitespace>at
649 int at = token.indexOf( "at" );
650 if ( at != -1 && token.substring( 0, at ).trim().length() == 0 )
651 {
652 traceStarted = true;
653 list.add( token );
654 }
655 else if ( traceStarted )
656 {
657 break;
658 }
659 }
660 return list;
661 }
662 }