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   * @version $Id$
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[list.size()] );
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      * @return The cause of the <code>Throwable</code>.
162      * @throws NullPointerException if the method names array is null or contains null
163      * @throws NullPointerException if the throwable is null
164      */
165     public static Throwable getCause( Throwable throwable, String[] methodNames )
166     {
167         Throwable cause = getCauseUsingWellKnownTypes( throwable );
168         if ( cause == null )
169         {
170             for ( String methodName : methodNames )
171             {
172                 cause = getCauseUsingMethodName( throwable, methodName );
173                 if ( cause != null )
174                 {
175                     break;
176                 }
177             }
178 
179             if ( cause == null )
180             {
181                 cause = getCauseUsingFieldName( throwable, "detail" );
182             }
183         }
184         return cause;
185     }
186 
187     /**
188      * <p>
189      * Walks through the exception chain to the last element -- the "root" of the tree -- using
190      * {@link #getCause(Throwable)}, and returns that exception.
191      * </p>
192      *
193      * @param throwable the throwable to get the root cause for
194      * @return The root cause of the <code>Throwable</code>.
195      */
196     public static Throwable getRootCause( Throwable throwable )
197     {
198         Throwable cause = getCause( throwable );
199         if ( cause != null )
200         {
201             throwable = cause;
202             while ( ( throwable = getCause( throwable ) ) != null )
203             {
204                 cause = throwable;
205             }
206         }
207         return cause;
208     }
209 
210     /**
211      * <p>
212      * Uses <code>instanceof</code> checks to examine the exception, looking for well known types which could contain
213      * chained or wrapped exceptions.
214      * </p>
215      *
216      * @param throwable the exception to examine
217      * @return The wrapped exception, or <code>null</code> if not found.
218      */
219     private static Throwable getCauseUsingWellKnownTypes( Throwable throwable )
220     {
221         if ( throwable instanceof SQLException )
222         {
223             return ( (SQLException) throwable ).getNextException();
224         }
225         else if ( throwable instanceof InvocationTargetException )
226         {
227             return ( (InvocationTargetException) throwable ).getTargetException();
228         }
229         else
230         {
231             return null;
232         }
233     }
234 
235     /**
236      * <p>
237      * Find a throwable by method name.
238      * </p>
239      *
240      * @param throwable the exception to examine
241      * @param methodName the name of the method to find and invoke
242      * @return The wrapped exception, or <code>null</code> if not found.
243      */
244     private static Throwable getCauseUsingMethodName( Throwable throwable, String methodName )
245     {
246         Method method = null;
247         try
248         {
249             method = throwable.getClass().getMethod( methodName, null );
250         }
251         catch ( NoSuchMethodException ignored )
252         {
253         }
254         catch ( SecurityException ignored )
255         {
256         }
257 
258         if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) )
259         {
260             try
261             {
262                 return (Throwable) method.invoke( throwable, new Object[0] );
263             }
264             catch ( IllegalAccessException ignored )
265             {
266             }
267             catch ( IllegalArgumentException ignored )
268             {
269             }
270             catch ( InvocationTargetException ignored )
271             {
272             }
273         }
274         return null;
275     }
276 
277     /**
278      * <p>
279      * Find a throwable by field name.
280      * </p>
281      *
282      * @param throwable the exception to examine
283      * @param fieldName the name of the attribute to examine
284      * @return The wrapped exception, or <code>null</code> if not found.
285      */
286     private static Throwable getCauseUsingFieldName( Throwable throwable, String fieldName )
287     {
288         Field field = null;
289         try
290         {
291             field = throwable.getClass().getField( fieldName );
292         }
293         catch ( NoSuchFieldException ignored )
294         {
295         }
296         catch ( SecurityException ignored )
297         {
298         }
299 
300         if ( field != null && Throwable.class.isAssignableFrom( field.getType() ) )
301         {
302             try
303             {
304                 return (Throwable) field.get( throwable );
305             }
306             catch ( IllegalAccessException ignored )
307             {
308             }
309             catch ( IllegalArgumentException ignored )
310             {
311             }
312         }
313         return null;
314     }
315 
316     /**
317      * <p>
318      * Returns the number of <code>Throwable</code> objects in the exception chain.
319      * </p>
320      *
321      * @param throwable the exception to inspect
322      * @return The throwable count.
323      */
324     public static int getThrowableCount( Throwable throwable )
325     {
326         // Count the number of throwables
327         int count = 0;
328         while ( throwable != null )
329         {
330             count++;
331             throwable = ExceptionUtils.getCause( throwable );
332         }
333         return count;
334     }
335 
336     /**
337      * <p>
338      * Returns the list of <code>Throwable</code> objects in the exception chain.
339      * </p>
340      *
341      * @param throwable the exception to inspect
342      * @return The list of <code>Throwable</code> objects.
343      */
344     public static Throwable[] getThrowables( Throwable throwable )
345     {
346         List<Throwable> list = new ArrayList<Throwable>();
347         while ( throwable != null )
348         {
349             list.add( throwable );
350             throwable = getCause( throwable );
351         }
352         return list.toArray( new Throwable[list.size()] );
353     }
354 
355     /**
356      * <p>
357      * Delegates to {@link #indexOfThrowable(Throwable, Class, int)}, starting the search at the beginning of the
358      * exception chain.
359      * </p>
360      *
361      * @see #indexOfThrowable(Throwable, Class, int)
362      */
363     public static int indexOfThrowable( Throwable throwable, Class type )
364     {
365         return indexOfThrowable( throwable, type, 0 );
366     }
367 
368     /**
369      * <p>
370      * Returns the (zero based) index, of the first <code>Throwable</code> that matches the specified type in the
371      * exception chain of <code>Throwable</code> objects with an index greater than or equal to the specified index, or
372      * <code>-1</code> if the type is not found.
373      * </p>
374      *
375      * @param throwable the exception to inspect
376      * @param type <code>Class</code> to look for
377      * @param fromIndex the (zero based) index of the starting position in the chain to be searched
378      * @return the first occurrence of the type in the chain, or <code>-1</code> if the type is not found
379      * @throws IndexOutOfBoundsException If the <code>fromIndex</code> argument is negative or not less than the count
380      *             of <code>Throwable</code>s in the chain.
381      */
382     public static int indexOfThrowable( Throwable throwable, Class type, int fromIndex )
383     {
384         if ( fromIndex < 0 )
385         {
386             throw new IndexOutOfBoundsException( "Throwable index out of range: " + fromIndex );
387         }
388         Throwable[] throwables = ExceptionUtils.getThrowables( throwable );
389         if ( fromIndex >= throwables.length )
390         {
391             throw new IndexOutOfBoundsException( "Throwable index out of range: " + fromIndex );
392         }
393         for ( int i = fromIndex; i < throwables.length; i++ )
394         {
395             if ( throwables[i].getClass().equals( type ) )
396             {
397                 return i;
398             }
399         }
400         return -1;
401     }
402 
403     /**
404      * Prints a compact stack trace for the root cause of a throwable. The compact stack trace starts with the root
405      * cause and prints stack frames up to the place where it was caught and wrapped. Then it prints the wrapped
406      * exception and continues with stack frames until the wrapper exception is caught and wrapped again, etc.
407      * <p>
408      * The method is equivalent to t.printStackTrace() for throwables that don't have nested causes.
409      */
410     public static void printRootCauseStackTrace( Throwable t, PrintStream stream )
411     {
412         String trace[] = getRootCauseStackTrace( t );
413         for ( String aTrace : trace )
414         {
415             stream.println( aTrace );
416         }
417         stream.flush();
418     }
419 
420     /**
421      * Equivalent to printRootCauseStackTrace(t, System.err)
422      */
423     public static void printRootCauseStackTrace( Throwable t )
424     {
425         printRootCauseStackTrace( t, System.err );
426     }
427 
428     /**
429      * Same as printRootCauseStackTrace(t, stream), except it takes a PrintWriter as an argument.
430      */
431     public static void printRootCauseStackTrace( Throwable t, PrintWriter writer )
432     {
433         String trace[] = getRootCauseStackTrace( t );
434         for ( String aTrace : trace )
435         {
436             writer.println( aTrace );
437         }
438         writer.flush();
439     }
440 
441     /**
442      * Creates a compact stack trace for the root cause of the supplied throwable. See
443      * <code>printRootCauseStackTrace(Throwable t, PrintStream s)</code>
444      */
445     public static String[] getRootCauseStackTrace( Throwable t )
446     {
447         Throwable[] throwables = getThrowables( t );
448         int count = throwables.length;
449         ArrayList<String> frames = new ArrayList<String>();
450         List<String> nextTrace = getStackFrameList( throwables[count - 1] );
451         for ( int i = count; --i >= 0; )
452         {
453             List<String> trace = nextTrace;
454             if ( i != 0 )
455             {
456                 nextTrace = getStackFrameList( throwables[i - 1] );
457                 removeCommonFrames( trace, nextTrace );
458             }
459             if ( i == ( count - 1 ) )
460             {
461                 frames.add( throwables[i].toString() );
462             }
463             else
464             {
465                 frames.add( WRAPPED_MARKER + throwables[i].toString() );
466             }
467             for ( String aTrace : trace )
468             {
469                 frames.add( aTrace );
470             }
471         }
472         return frames.toArray( new String[frames.size()] );
473     }
474 
475     /**
476      * Given two stack traces, removes common frames from the cause trace.
477      *
478      * @param causeFrames stack trace of a cause throwable
479      * @param wrapperFrames stack trace of a wrapper throwable
480      */
481     private static void removeCommonFrames( List<String> causeFrames, List<String> wrapperFrames )
482     {
483         int causeFrameIndex = causeFrames.size() - 1;
484         int wrapperFrameIndex = wrapperFrames.size() - 1;
485         while ( causeFrameIndex >= 0 && wrapperFrameIndex >= 0 )
486         {
487             // Remove the frame from the cause trace if it is the same
488             // as in the wrapper trace
489             String causeFrame = causeFrames.get( causeFrameIndex );
490             String wrapperFrame = wrapperFrames.get( wrapperFrameIndex );
491             if ( causeFrame.equals( wrapperFrame ) )
492             {
493                 causeFrames.remove( causeFrameIndex );
494             }
495             causeFrameIndex--;
496             wrapperFrameIndex--;
497         }
498     }
499 
500     /**
501      * A convenient way of extracting the stack trace from an exception.
502      *
503      * @param t The <code>Throwable</code>.
504      * @return The stack trace as generated by the exception's <code>printStackTrace(PrintWriter)</code> method.
505      */
506     public static String getStackTrace( Throwable t )
507     {
508         StringWriter sw = new StringWriter();
509         PrintWriter pw = new PrintWriter( sw, true );
510         t.printStackTrace( pw );
511         return sw.getBuffer().toString();
512     }
513 
514     /**
515      * A way to get the entire nested stack-trace of an throwable.
516      *
517      * @param t The <code>Throwable</code>.
518      * @return The nested stack trace, with the root cause first.
519      */
520     public static String getFullStackTrace( Throwable t )
521     {
522         StringWriter sw = new StringWriter();
523         PrintWriter pw = new PrintWriter( sw, true );
524         Throwable[] ts = getThrowables( t );
525         for ( Throwable t1 : ts )
526         {
527             t1.printStackTrace( pw );
528             if ( isNestedThrowable( t1 ) )
529             {
530                 break;
531             }
532         }
533         return sw.getBuffer().toString();
534     }
535 
536     /**
537      * Whether an Throwable is considered nested or not.
538      *
539      * @param throwable The <code>Throwable</code>.
540      * @return boolean true/false
541      */
542     public static boolean isNestedThrowable( Throwable throwable )
543     {
544         if ( throwable == null )
545         {
546             return false;
547         }
548 
549         if ( throwable instanceof SQLException )
550         {
551             return true;
552         }
553         else if ( throwable instanceof InvocationTargetException )
554         {
555             return true;
556         }
557 
558         for ( String CAUSE_METHOD_NAME : CAUSE_METHOD_NAMES )
559         {
560             try
561             {
562                 Method method = throwable.getClass().getMethod( CAUSE_METHOD_NAME, null );
563                 if ( method != null )
564                 {
565                     return true;
566                 }
567             }
568             catch ( NoSuchMethodException ignored )
569             {
570             }
571             catch ( SecurityException ignored )
572             {
573             }
574         }
575 
576         try
577         {
578             Field field = throwable.getClass().getField( "detail" );
579             if ( field != null )
580             {
581                 return true;
582             }
583         }
584         catch ( NoSuchFieldException ignored )
585         {
586         }
587         catch ( SecurityException ignored )
588         {
589         }
590 
591         return false;
592     }
593 
594     /**
595      * Captures the stack trace associated with the specified <code>Throwable</code> object, decomposing it into a list
596      * of stack frames.
597      *
598      * @param t The <code>Throwable</code>.
599      * @return An array of strings describing each stack frame.
600      */
601     public static String[] getStackFrames( Throwable t )
602     {
603         return getStackFrames( getStackTrace( t ) );
604     }
605 
606     /**
607      * Functionality shared between the <code>getStackFrames(Throwable)</code> methods of this and the classes.
608      */
609     static String[] getStackFrames( String stackTrace )
610     {
611         String linebreak = System.getProperty( "line.separator" );
612         StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
613         List<String> list = new LinkedList<String>();
614         while ( frames.hasMoreTokens() )
615         {
616             list.add( frames.nextToken() );
617         }
618         return list.toArray( new String[list.size()] );
619     }
620 
621     /**
622      * Produces a List of stack frames - the message is not included. This works in most cases - it will only fail if
623      * the exception message contains a line that starts with: " at".
624      *
625      * @param t is any throwable
626      * @return List of stack frames
627      */
628     static List<String> getStackFrameList( Throwable t )
629     {
630         String stackTrace = getStackTrace( t );
631         String linebreak = System.getProperty( "line.separator" );
632         StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
633         List<String> list = new LinkedList<String>();
634         boolean traceStarted = false;
635         while ( frames.hasMoreTokens() )
636         {
637             String token = frames.nextToken();
638             // Determine if the line starts with <whitespace>at
639             int at = token.indexOf( "at" );
640             if ( at != -1 && token.substring( 0, at ).trim().length() == 0 )
641             {
642                 traceStarted = true;
643                 list.add( token );
644             }
645             else if ( traceStarted )
646             {
647                 break;
648             }
649         }
650         return list;
651     }
652 }