Coverage Report - org.codehaus.plexus.util.ExceptionUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
ExceptionUtils
0%
0/166
0%
0/84
3.913
 
 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  0
     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  0
     {
 100  0
     }
 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  0
         if ( methodName != null && methodName.length() > 0 )
 112  
         {
 113  0
             List<String> list = new ArrayList<String>( Arrays.asList( CAUSE_METHOD_NAMES ) );
 114  0
             list.add( methodName );
 115  0
             CAUSE_METHOD_NAMES = list.toArray( new String[list.size()] );
 116  
         }
 117  0
     }
 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  0
         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  0
         Throwable cause = getCauseUsingWellKnownTypes( throwable );
 168  0
         if ( cause == null )
 169  
         {
 170  0
             for ( String methodName : methodNames )
 171  
             {
 172  0
                 cause = getCauseUsingMethodName( throwable, methodName );
 173  0
                 if ( cause != null )
 174  
                 {
 175  0
                     break;
 176  
                 }
 177  
             }
 178  
 
 179  0
             if ( cause == null )
 180  
             {
 181  0
                 cause = getCauseUsingFieldName( throwable, "detail" );
 182  
             }
 183  
         }
 184  0
         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  0
         Throwable cause = getCause( throwable );
 199  0
         if ( cause != null )
 200  
         {
 201  0
             throwable = cause;
 202  0
             while ( ( throwable = getCause( throwable ) ) != null )
 203  
             {
 204  0
                 cause = throwable;
 205  
             }
 206  
         }
 207  0
         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  0
         if ( throwable instanceof SQLException )
 222  
         {
 223  0
             return ( (SQLException) throwable ).getNextException();
 224  
         }
 225  0
         else if ( throwable instanceof InvocationTargetException )
 226  
         {
 227  0
             return ( (InvocationTargetException) throwable ).getTargetException();
 228  
         }
 229  
         else
 230  
         {
 231  0
             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  0
         Method method = null;
 247  
         try
 248  
         {
 249  0
             method = throwable.getClass().getMethod( methodName, null );
 250  
         }
 251  0
         catch ( NoSuchMethodException ignored )
 252  
         {
 253  
         }
 254  0
         catch ( SecurityException ignored )
 255  
         {
 256  0
         }
 257  
 
 258  0
         if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) )
 259  
         {
 260  
             try
 261  
             {
 262  0
                 return (Throwable) method.invoke( throwable, new Object[0] );
 263  
             }
 264  0
             catch ( IllegalAccessException ignored )
 265  
             {
 266  
             }
 267  0
             catch ( IllegalArgumentException ignored )
 268  
             {
 269  
             }
 270  0
             catch ( InvocationTargetException ignored )
 271  
             {
 272  0
             }
 273  
         }
 274  0
         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  0
         Field field = null;
 289  
         try
 290  
         {
 291  0
             field = throwable.getClass().getField( fieldName );
 292  
         }
 293  0
         catch ( NoSuchFieldException ignored )
 294  
         {
 295  
         }
 296  0
         catch ( SecurityException ignored )
 297  
         {
 298  0
         }
 299  
 
 300  0
         if ( field != null && Throwable.class.isAssignableFrom( field.getType() ) )
 301  
         {
 302  
             try
 303  
             {
 304  0
                 return (Throwable) field.get( throwable );
 305  
             }
 306  0
             catch ( IllegalAccessException ignored )
 307  
             {
 308  
             }
 309  0
             catch ( IllegalArgumentException ignored )
 310  
             {
 311  0
             }
 312  
         }
 313  0
         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  0
         int count = 0;
 328  0
         while ( throwable != null )
 329  
         {
 330  0
             count++;
 331  0
             throwable = ExceptionUtils.getCause( throwable );
 332  
         }
 333  0
         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  0
         List<Throwable> list = new ArrayList<Throwable>();
 347  0
         while ( throwable != null )
 348  
         {
 349  0
             list.add( throwable );
 350  0
             throwable = getCause( throwable );
 351  
         }
 352  0
         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  0
         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  0
         if ( fromIndex < 0 )
 385  
         {
 386  0
             throw new IndexOutOfBoundsException( "Throwable index out of range: " + fromIndex );
 387  
         }
 388  0
         Throwable[] throwables = ExceptionUtils.getThrowables( throwable );
 389  0
         if ( fromIndex >= throwables.length )
 390  
         {
 391  0
             throw new IndexOutOfBoundsException( "Throwable index out of range: " + fromIndex );
 392  
         }
 393  0
         for ( int i = fromIndex; i < throwables.length; i++ )
 394  
         {
 395  0
             if ( throwables[i].getClass().equals( type ) )
 396  
             {
 397  0
                 return i;
 398  
             }
 399  
         }
 400  0
         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  0
         String trace[] = getRootCauseStackTrace( t );
 413  0
         for ( String aTrace : trace )
 414  
         {
 415  0
             stream.println( aTrace );
 416  
         }
 417  0
         stream.flush();
 418  0
     }
 419  
 
 420  
     /**
 421  
      * Equivalent to printRootCauseStackTrace(t, System.err)
 422  
      */
 423  
     public static void printRootCauseStackTrace( Throwable t )
 424  
     {
 425  0
         printRootCauseStackTrace( t, System.err );
 426  0
     }
 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  0
         String trace[] = getRootCauseStackTrace( t );
 434  0
         for ( String aTrace : trace )
 435  
         {
 436  0
             writer.println( aTrace );
 437  
         }
 438  0
         writer.flush();
 439  0
     }
 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  0
         Throwable[] throwables = getThrowables( t );
 448  0
         int count = throwables.length;
 449  0
         ArrayList<String> frames = new ArrayList<String>();
 450  0
         List<String> nextTrace = getStackFrameList( throwables[count - 1] );
 451  0
         for ( int i = count; --i >= 0; )
 452  
         {
 453  0
             List<String> trace = nextTrace;
 454  0
             if ( i != 0 )
 455  
             {
 456  0
                 nextTrace = getStackFrameList( throwables[i - 1] );
 457  0
                 removeCommonFrames( trace, nextTrace );
 458  
             }
 459  0
             if ( i == ( count - 1 ) )
 460  
             {
 461  0
                 frames.add( throwables[i].toString() );
 462  
             }
 463  
             else
 464  
             {
 465  0
                 frames.add( WRAPPED_MARKER + throwables[i].toString() );
 466  
             }
 467  0
             for ( String aTrace : trace )
 468  
             {
 469  0
                 frames.add( aTrace );
 470  0
             }
 471  0
         }
 472  0
         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  0
         int causeFrameIndex = causeFrames.size() - 1;
 484  0
         int wrapperFrameIndex = wrapperFrames.size() - 1;
 485  0
         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  0
             String causeFrame = causeFrames.get( causeFrameIndex );
 490  0
             String wrapperFrame = wrapperFrames.get( wrapperFrameIndex );
 491  0
             if ( causeFrame.equals( wrapperFrame ) )
 492  
             {
 493  0
                 causeFrames.remove( causeFrameIndex );
 494  
             }
 495  0
             causeFrameIndex--;
 496  0
             wrapperFrameIndex--;
 497  0
         }
 498  0
     }
 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  0
         StringWriter sw = new StringWriter();
 509  0
         PrintWriter pw = new PrintWriter( sw, true );
 510  0
         t.printStackTrace( pw );
 511  0
         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  0
         StringWriter sw = new StringWriter();
 523  0
         PrintWriter pw = new PrintWriter( sw, true );
 524  0
         Throwable[] ts = getThrowables( t );
 525  0
         for ( Throwable t1 : ts )
 526  
         {
 527  0
             t1.printStackTrace( pw );
 528  0
             if ( isNestedThrowable( t1 ) )
 529  
             {
 530  0
                 break;
 531  
             }
 532  
         }
 533  0
         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  0
         if ( throwable == null )
 545  
         {
 546  0
             return false;
 547  
         }
 548  
 
 549  0
         if ( throwable instanceof SQLException )
 550  
         {
 551  0
             return true;
 552  
         }
 553  0
         else if ( throwable instanceof InvocationTargetException )
 554  
         {
 555  0
             return true;
 556  
         }
 557  
 
 558  0
         for ( String CAUSE_METHOD_NAME : CAUSE_METHOD_NAMES )
 559  
         {
 560  
             try
 561  
             {
 562  0
                 Method method = throwable.getClass().getMethod( CAUSE_METHOD_NAME, null );
 563  0
                 if ( method != null )
 564  
                 {
 565  0
                     return true;
 566  
                 }
 567  
             }
 568  0
             catch ( NoSuchMethodException ignored )
 569  
             {
 570  
             }
 571  0
             catch ( SecurityException ignored )
 572  
             {
 573  0
             }
 574  
         }
 575  
 
 576  
         try
 577  
         {
 578  0
             Field field = throwable.getClass().getField( "detail" );
 579  0
             if ( field != null )
 580  
             {
 581  0
                 return true;
 582  
             }
 583  
         }
 584  0
         catch ( NoSuchFieldException ignored )
 585  
         {
 586  
         }
 587  0
         catch ( SecurityException ignored )
 588  
         {
 589  0
         }
 590  
 
 591  0
         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  0
         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  0
         String linebreak = System.getProperty( "line.separator" );
 612  0
         StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
 613  0
         List<String> list = new LinkedList<String>();
 614  0
         while ( frames.hasMoreTokens() )
 615  
         {
 616  0
             list.add( frames.nextToken() );
 617  
         }
 618  0
         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  0
         String stackTrace = getStackTrace( t );
 631  0
         String linebreak = System.getProperty( "line.separator" );
 632  0
         StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
 633  0
         List<String> list = new LinkedList<String>();
 634  0
         boolean traceStarted = false;
 635  0
         while ( frames.hasMoreTokens() )
 636  
         {
 637  0
             String token = frames.nextToken();
 638  
             // Determine if the line starts with <whitespace>at
 639  0
             int at = token.indexOf( "at" );
 640  0
             if ( at != -1 && token.substring( 0, at ).trim().length() == 0 )
 641  
             {
 642  0
                 traceStarted = true;
 643  0
                 list.add( token );
 644  
             }
 645  0
             else if ( traceStarted )
 646  
             {
 647  0
                 break;
 648  
             }
 649  0
         }
 650  0
         return list;
 651  
     }
 652  
 }