View Javadoc
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 }