001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.shared.ldap.util;
021
022
023 import java.io.PrintStream;
024 import java.io.PrintWriter;
025 import java.io.StringWriter;
026 import java.lang.reflect.Field;
027 import java.lang.reflect.InvocationTargetException;
028 import java.lang.reflect.Method;
029 import java.sql.SQLException;
030 import java.util.ArrayList;
031 import java.util.Arrays;
032 import java.util.LinkedList;
033 import java.util.List;
034 import java.util.StringTokenizer;
035
036
037 /**
038 * <p>
039 * Provides utilities for manipulating and examining <code>Throwable</code>
040 * objects.
041 * </p>
042 *
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 * @version $Rev: 494173 $
045 */
046 public class ExceptionUtils
047 {
048
049 /**
050 * <p>
051 * Used when printing stack frames to denote the start of a wrapped
052 * exception.
053 * </p>
054 * <p/>
055 * <p>
056 * Package private for accessibility by test suite.
057 * </p>
058 */
059 static final String WRAPPED_MARKER = " [wrapped] ";
060
061 /**
062 * <p>
063 * The names of methods commonly used to access a wrapped exception.
064 * </p>
065 */
066 private static String[] CAUSE_METHOD_NAMES =
067 { "getCause", "getNextException", "getTargetException", "getException", "getSourceException", "getRootCause",
068 "getCausedByException", "getNested", "getLinkedException", "getNestedException", "getLinkedCause",
069 "getThrowable", };
070
071 /**
072 * <p>
073 * The Method object for JDK1.4 getCause.
074 * </p>
075 */
076 private static final Method THROWABLE_CAUSE_METHOD;
077
078 static
079 {
080 Method getCauseMethod;
081 try
082 {
083 getCauseMethod = Throwable.class.getMethod( "getCause", (Class[])null );
084 }
085 catch ( Exception e )
086 {
087 getCauseMethod = null;
088 }
089 THROWABLE_CAUSE_METHOD = getCauseMethod;
090 }
091
092
093 /**
094 * <p>
095 * Public constructor allows an instance of <code>ExceptionUtils</code> to
096 * be created, although that is not normally necessary.
097 * </p>
098 */
099 public ExceptionUtils()
100 {
101 }
102
103
104 /**
105 * <p>
106 * Checks if a String is not empty ("") and not null.
107 * </p>
108 *
109 * @param str
110 * the String to check, may be null
111 * @return <code>true</code> if the String is not empty and not null
112 */
113 private static boolean isNotEmpty( String str )
114 {
115 return ( str != null && str.length() > 0 );
116 }
117
118
119 // -----------------------------------------------------------------------
120 /**
121 * <p>
122 * Adds to the list of method names used in the search for
123 * <code>Throwable</code> objects.
124 * </p>
125 *
126 * @param methodName
127 * the methodName to add to the list, <code>null</code> and
128 * empty strings are ignored
129 * @since 2.0
130 */
131 public static void addCauseMethodName( String methodName )
132 {
133 if ( isNotEmpty( methodName ) )
134 {
135 List<String> list = new ArrayList<String>( Arrays.asList( CAUSE_METHOD_NAMES ) );
136 list.add( methodName );
137 CAUSE_METHOD_NAMES = list.toArray( new String[list.size()] );
138 }
139 }
140
141
142 /**
143 * <p>
144 * Introspects the <code>Throwable</code> to obtain the cause.
145 * </p>
146 * <p/>
147 * <p>
148 * The method searches for methods with specific names that return a
149 * <code>Throwable</code> object. This will pick up most wrapping
150 * exceptions, including those from JDK 1.4, and The method names can be
151 * added to using {@link #addCauseMethodName(String)}.
152 * </p>
153 * <p/>
154 * <p>
155 * The default list searched for are:
156 * </p>
157 * <ul>
158 * <li><code>getCause()</code></li>
159 * <li><code>getNextException()</code></li>
160 * <li><code>getTargetException()</code></li>
161 * <li><code>getException()</code></li>
162 * <li><code>getSourceException()</code></li>
163 * <li><code>getRootCause()</code></li>
164 * <li><code>getCausedByException()</code></li>
165 * <li><code>getNested()</code></li>
166 * </ul>
167 * <p/>
168 * <p>
169 * In the absence of any such method, the object is inspected for a
170 * <code>detail</code> field assignable to a <code>Throwable</code>.
171 * </p>
172 * <p/>
173 * <p>
174 * If none of the above is found, returns <code>null</code>.
175 * </p>
176 *
177 * @param throwable
178 * the throwable to introspect for a cause, may be null
179 * @return the cause of the <code>Throwable</code>, <code>null</code>
180 * if none found or null throwable input
181 * @since 1.0
182 */
183 public static Throwable getCause( Throwable throwable )
184 {
185 return getCause( throwable, CAUSE_METHOD_NAMES );
186 }
187
188
189 /**
190 * <p>
191 * Introspects the <code>Throwable</code> to obtain the cause.
192 * </p>
193 * <p/>
194 * <ol>
195 * <li>Try known exception types.</li>
196 * <li>Try the supplied array of method names.</li>
197 * <li>Try the field 'detail'.</li>
198 * </ol>
199 * <p/>
200 * <p>
201 * A <code>null</code> set of method names means use the default set. A
202 * <code>null</code> in the set of method names will be ignored.
203 * </p>
204 *
205 * @param throwable
206 * the throwable to introspect for a cause, may be null
207 * @param methodNames
208 * the method names, null treated as default set
209 * @return the cause of the <code>Throwable</code>, <code>null</code>
210 * if none found or null throwable input
211 * @since 1.0
212 */
213 public static Throwable getCause( Throwable throwable, String[] methodNames )
214 {
215 if ( throwable == null )
216 {
217 return null;
218 }
219 Throwable cause = getCauseUsingWellKnownTypes( throwable );
220 if ( cause == null )
221 {
222 if ( methodNames == null )
223 {
224 methodNames = CAUSE_METHOD_NAMES;
225 }
226 for ( int i = 0; i < methodNames.length; i++ )
227 {
228 String methodName = methodNames[i];
229 if ( methodName != null )
230 {
231 cause = getCauseUsingMethodName( throwable, methodName );
232 if ( cause != null )
233 {
234 break;
235 }
236 }
237 }
238
239 if ( cause == null )
240 {
241 cause = getCauseUsingFieldName( throwable, "detail" );
242 }
243 }
244 return cause;
245 }
246
247
248 /**
249 * <p>
250 * Introspects the <code>Throwable</code> to obtain the root cause.
251 * </p>
252 * <p/>
253 * <p>
254 * This method walks through the exception chain to the last element, "root"
255 * of the tree, using {@link #getCause(Throwable)}, and returns that
256 * exception.
257 * </p>
258 *
259 * @param throwable
260 * the throwable to get the root cause for, may be null
261 * @return the root cause of the <code>Throwable</code>,
262 * <code>null</code> if none found or null throwable input
263 */
264 public static Throwable getRootCause( Throwable throwable )
265 {
266 Throwable cause = getCause( throwable );
267 if ( cause != null )
268 {
269 throwable = cause;
270 while ( ( throwable = getCause( throwable ) ) != null )
271 {
272 cause = throwable;
273 }
274 }
275 return cause;
276 }
277
278
279 /**
280 * <p>
281 * Finds a <code>Throwable</code> for known types.
282 * </p>
283 * <p/>
284 * <p>
285 * Uses <code>instanceof</code> checks to examine the exception, looking
286 * for well known types which could contain chained or wrapped exceptions.
287 * </p>
288 *
289 * @param throwable
290 * the exception to examine
291 * @return the wrapped exception, or <code>null</code> if not found
292 */
293 private static Throwable getCauseUsingWellKnownTypes( Throwable throwable )
294 {
295 if ( throwable instanceof Nestable )
296 {
297 return ( ( Nestable ) throwable ).getCause();
298 }
299 else if ( throwable instanceof SQLException )
300 {
301 return ( ( SQLException ) throwable ).getNextException();
302 }
303 else if ( throwable instanceof InvocationTargetException )
304 {
305 return ( ( InvocationTargetException ) throwable ).getTargetException();
306 }
307 else
308 {
309 return null;
310 }
311 }
312
313
314 /**
315 * <p>
316 * Finds a <code>Throwable</code> by method name.
317 * </p>
318 *
319 * @param throwable
320 * the exception to examine
321 * @param methodName
322 * the name of the method to find and invoke
323 * @return the wrapped exception, or <code>null</code> if not found
324 */
325 private static Throwable getCauseUsingMethodName( Throwable throwable, String methodName )
326 {
327 Method method = null;
328 try
329 {
330 method = throwable.getClass().getMethod( methodName, (Class[])null );
331 }
332 catch ( NoSuchMethodException ignored )
333 {
334 }
335 catch ( SecurityException ignored )
336 {
337 }
338
339 if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) )
340 {
341 try
342 {
343 return ( Throwable ) method.invoke( throwable, ArrayUtils.EMPTY_OBJECT_ARRAY );
344 }
345 catch ( IllegalAccessException ignored )
346 {
347 }
348 catch ( IllegalArgumentException ignored )
349 {
350 }
351 catch ( InvocationTargetException ignored )
352 {
353 }
354 }
355 return null;
356 }
357
358
359 /**
360 * <p>
361 * Finds a <code>Throwable</code> by field name.
362 * </p>
363 *
364 * @param throwable
365 * the exception to examine
366 * @param fieldName
367 * the name of the attribute to examine
368 * @return the wrapped exception, or <code>null</code> if not found
369 */
370 private static Throwable getCauseUsingFieldName( Throwable throwable, String fieldName )
371 {
372 Field field = null;
373 try
374 {
375 field = throwable.getClass().getField( fieldName );
376 }
377 catch ( NoSuchFieldException ignored )
378 {
379 }
380 catch ( SecurityException ignored )
381 {
382 }
383
384 if ( field != null && Throwable.class.isAssignableFrom( field.getType() ) )
385 {
386 try
387 {
388 return ( Throwable ) field.get( throwable );
389 }
390 catch ( IllegalAccessException ignored )
391 {
392 }
393 catch ( IllegalArgumentException ignored )
394 {
395 }
396 }
397 return null;
398 }
399
400
401 // -----------------------------------------------------------------------
402 /**
403 * <p>
404 * Checks if the Throwable class has a <code>getCause</code> method.
405 * </p>
406 * <p/>
407 * <p>
408 * This is true for JDK 1.4 and above.
409 * </p>
410 *
411 * @return true if Throwable is nestable
412 * @since 2.0
413 */
414 public static boolean isThrowableNested()
415 {
416 return ( THROWABLE_CAUSE_METHOD != null );
417 }
418
419
420 /**
421 * <p>
422 * Checks whether this <code>Throwable</code> class can store a cause.
423 * </p>
424 * <p/>
425 * <p>
426 * This method does <b>not</b> check whether it actually does store a
427 * cause.
428 * <p>
429 *
430 * @param throwable
431 * the <code>Throwable</code> to examine, may be null
432 * @return boolean <code>true</code> if nested otherwise
433 * <code>false</code>
434 * @since 2.0
435 */
436 public static boolean isNestedThrowable( Throwable throwable )
437 {
438 if ( throwable == null )
439 {
440 return false;
441 }
442
443 if ( throwable instanceof Nestable )
444 {
445 return true;
446 }
447 else if ( throwable instanceof SQLException )
448 {
449 return true;
450 }
451 else if ( throwable instanceof InvocationTargetException )
452 {
453 return true;
454 }
455 else if ( isThrowableNested() )
456 {
457 return true;
458 }
459
460 Class cls = throwable.getClass();
461 for ( int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++ )
462 {
463 try
464 {
465 Method method = cls.getMethod( CAUSE_METHOD_NAMES[i], (Class[])null );
466 if ( method != null && Throwable.class.isAssignableFrom( method.getReturnType() ) )
467 {
468 return true;
469 }
470 }
471 catch ( NoSuchMethodException ignored )
472 {
473 }
474 catch ( SecurityException ignored )
475 {
476 }
477 }
478
479 try
480 {
481 Field field = cls.getField( "detail" );
482 if ( field != null )
483 {
484 return true;
485 }
486 }
487 catch ( NoSuchFieldException ignored )
488 {
489 }
490 catch ( SecurityException ignored )
491 {
492 }
493
494 return false;
495 }
496
497
498 // -----------------------------------------------------------------------
499 /**
500 * <p>
501 * Counts the number of <code>Throwable</code> objects in the exception
502 * chain.
503 * </p>
504 * <p/>
505 * <p>
506 * A throwable without cause will return <code>1</code>. A throwable with
507 * one cause will return <code>2</code> and so on. A <code>null</code>
508 * throwable will return <code>0</code>.
509 * </p>
510 *
511 * @param throwable
512 * the throwable to inspect, may be null
513 * @return the count of throwables, zero if null input
514 */
515 public static int getThrowableCount( Throwable throwable )
516 {
517 int count = 0;
518 while ( throwable != null )
519 {
520 count++;
521 throwable = ExceptionUtils.getCause( throwable );
522 }
523 return count;
524 }
525
526
527 /**
528 * <p>
529 * Returns the list of <code>Throwable</code> objects in the exception
530 * chain.
531 * </p>
532 * <p/>
533 * <p>
534 * A throwable without cause will return an array containing one element -
535 * the input throwable. A throwable with one cause will return an array
536 * containing two elements. - the input throwable and the cause throwable. A
537 * <code>null</code> throwable will return an array size zero.
538 * </p>
539 *
540 * @param throwable
541 * the throwable to inspect, may be null
542 * @return the array of throwables, never null
543 */
544 public static Throwable[] getThrowables( Throwable throwable )
545 {
546 List<Throwable> list = new ArrayList<Throwable>();
547
548 while ( throwable != null )
549 {
550 list.add( throwable );
551 throwable = ExceptionUtils.getCause( throwable );
552 }
553
554 return list.toArray( new Throwable[list.size()] );
555 }
556
557
558 // -----------------------------------------------------------------------
559 /**
560 * <p>
561 * Returns the (zero based) index of the first <code>Throwable</code> that
562 * matches the specified type in the exception chain.
563 * </p>
564 * <p/>
565 * <p>
566 * A <code>null</code> throwable returns <code>-1</code>. A
567 * <code>null</code> type returns <code>-1</code>. No match in the
568 * chain returns <code>-1</code>.
569 * </p>
570 *
571 * @param throwable
572 * the throwable to inspect, may be null
573 * @param type
574 * the type to search for
575 * @return the index into the throwable chain, -1 if no match or null input
576 */
577 public static int indexOfThrowable( Throwable throwable, Class type )
578 {
579 return indexOfThrowable( throwable, type, 0 );
580 }
581
582
583 /**
584 * <p>
585 * Returns the (zero based) index of the first <code>Throwable</code> that
586 * matches the specified type in the exception chain from a specified index.
587 * </p>
588 * <p/>
589 * <p>
590 * A <code>null</code> throwable returns <code>-1</code>. A
591 * <code>null</code> type returns <code>-1</code>. No match in the
592 * chain returns <code>-1</code>. A negative start index is treated as
593 * zero. A start index greater than the number of throwables returns
594 * <code>-1</code>.
595 * </p>
596 *
597 * @param throwable
598 * the throwable to inspect, may be null
599 * @param type
600 * the type to search for
601 * @param fromIndex
602 * the (zero based) index of the starting position, negative
603 * treated as zero, larger than chain size returns -1
604 * @return the index into the throwable chain, -1 if no match or null input
605 */
606 public static int indexOfThrowable( Throwable throwable, Class type, int fromIndex )
607 {
608 if ( throwable == null )
609 {
610 return -1;
611 }
612 if ( fromIndex < 0 )
613 {
614 fromIndex = 0;
615 }
616 Throwable[] throwables = ExceptionUtils.getThrowables( throwable );
617 if ( fromIndex >= throwables.length )
618 {
619 return -1;
620 }
621 for ( int i = fromIndex; i < throwables.length; i++ )
622 {
623 if ( throwables[i].getClass().equals( type ) )
624 {
625 return i;
626 }
627 }
628 return -1;
629 }
630
631
632 // -----------------------------------------------------------------------
633 /**
634 * <p>
635 * Prints a compact stack trace for the root cause of a throwable to
636 * <code>System.err</code>.
637 * </p>
638 * <p/>
639 * <p>
640 * The compact stack trace starts with the root cause and prints stack
641 * frames up to the place where it was caught and wrapped. Then it prints
642 * the wrapped exception and continues with stack frames until the wrapper
643 * exception is caught and wrapped again, etc.
644 * </p>
645 * <p/>
646 * <p>
647 * The method is equivalent to <code>printStackTrace</code> for throwables
648 * that don't have nested causes.
649 * </p>
650 *
651 * @param throwable
652 * the throwable to output
653 * @since 2.0
654 */
655 public static void printRootCauseStackTrace( Throwable throwable )
656 {
657 printRootCauseStackTrace( throwable, System.err );
658 }
659
660
661 /**
662 * <p>
663 * Prints a compact stack trace for the root cause of a throwable.
664 * </p>
665 * <p/>
666 * <p>
667 * The compact stack trace starts with the root cause and prints stack
668 * frames up to the place where it was caught and wrapped. Then it prints
669 * the wrapped exception and continues with stack frames until the wrapper
670 * exception is caught and wrapped again, etc.
671 * </p>
672 * <p/>
673 * <p>
674 * The method is equivalent to <code>printStackTrace</code> for throwables
675 * that don't have nested causes.
676 * </p>
677 *
678 * @param throwable
679 * the throwable to output, may be null
680 * @param stream
681 * the stream to output to, may not be null
682 * @throws IllegalArgumentException
683 * if the stream is <code>null</code>
684 * @since 2.0
685 */
686 public static void printRootCauseStackTrace( Throwable throwable, PrintStream stream )
687 {
688 if ( throwable == null )
689 {
690 return;
691 }
692 if ( stream == null )
693 {
694 throw new IllegalArgumentException( "The PrintStream must not be null" );
695 }
696 String trace[] = getRootCauseStackTrace( throwable );
697 for ( int i = 0; i < trace.length; i++ )
698 {
699 stream.println( trace[i] );
700 }
701 stream.flush();
702 }
703
704
705 /**
706 * <p>
707 * Prints a compact stack trace for the root cause of a throwable.
708 * </p>
709 * <p/>
710 * <p>
711 * The compact stack trace starts with the root cause and prints stack
712 * frames up to the place where it was caught and wrapped. Then it prints
713 * the wrapped exception and continues with stack frames until the wrapper
714 * exception is caught and wrapped again, etc.
715 * </p>
716 * <p/>
717 * <p>
718 * The method is equivalent to <code>printStackTrace</code> for throwables
719 * that don't have nested causes.
720 * </p>
721 *
722 * @param throwable
723 * the throwable to output, may be null
724 * @param writer
725 * the writer to output to, may not be null
726 * @throws IllegalArgumentException
727 * if the writer is <code>null</code>
728 * @since 2.0
729 */
730 public static void printRootCauseStackTrace( Throwable throwable, PrintWriter writer )
731 {
732 if ( throwable == null )
733 {
734 return;
735 }
736 if ( writer == null )
737 {
738 throw new IllegalArgumentException( "The PrintWriter must not be null" );
739 }
740 String trace[] = getRootCauseStackTrace( throwable );
741 for ( int i = 0; i < trace.length; i++ )
742 {
743 writer.println( trace[i] );
744 }
745 writer.flush();
746 }
747
748
749 // -----------------------------------------------------------------------
750 /**
751 * <p>
752 * Creates a compact stack trace for the root cause of the supplied
753 * <code>Throwable</code>.
754 * </p>
755 *
756 * @param throwable
757 * the throwable to examine, may be null
758 * @return an array of stack trace frames, never null
759 * @since 2.0
760 */
761 public static String[] getRootCauseStackTrace( Throwable throwable )
762 {
763 if ( throwable == null )
764 {
765 return ArrayUtils.EMPTY_STRING_ARRAY;
766 }
767
768 Throwable throwables[] = getThrowables( throwable );
769 int count = throwables.length;
770 List<String> frames = new ArrayList<String>();
771 List<String> nextTrace = getStackFrameList( throwables[count - 1] );
772
773 for ( int i = count; --i >= 0; )
774 {
775 List<String> trace = nextTrace;
776
777 if ( i != 0 )
778 {
779 nextTrace = getStackFrameList( throwables[i - 1] );
780 removeCommonFrames( trace, nextTrace );
781 }
782 if ( i == count - 1 )
783 {
784 frames.add( throwables[i].toString() );
785 }
786 else
787 {
788 frames.add( WRAPPED_MARKER + throwables[i].toString() );
789 }
790 for ( int j = 0; j < trace.size(); j++ )
791 {
792 frames.add( trace.get( j ) );
793 }
794 }
795 return frames.toArray( new String[0] );
796 }
797
798
799 /**
800 * <p>
801 * Removes common frames from the cause trace given the two stack traces.
802 * </p>
803 *
804 * @param causeFrames
805 * stack trace of a cause throwable
806 * @param wrapperFrames
807 * stack trace of a wrapper throwable
808 * @throws IllegalArgumentException
809 * if either argument is null
810 * @since 2.0
811 */
812 public static void removeCommonFrames( List causeFrames, List wrapperFrames )
813 {
814 if ( causeFrames == null || wrapperFrames == null )
815 {
816 throw new IllegalArgumentException( "The List must not be null" );
817 }
818 int causeFrameIndex = causeFrames.size() - 1;
819 int wrapperFrameIndex = wrapperFrames.size() - 1;
820 while ( causeFrameIndex >= 0 && wrapperFrameIndex >= 0 )
821 {
822 // Remove the frame from the cause trace if it is the same
823 // as in the wrapper trace
824 String causeFrame = ( String ) causeFrames.get( causeFrameIndex );
825 String wrapperFrame = ( String ) wrapperFrames.get( wrapperFrameIndex );
826 if ( causeFrame.equals( wrapperFrame ) )
827 {
828 causeFrames.remove( causeFrameIndex );
829 }
830 causeFrameIndex--;
831 wrapperFrameIndex--;
832 }
833 }
834
835
836 // -----------------------------------------------------------------------
837 /**
838 * <p>
839 * Gets the stack trace from a Throwable as a String.
840 * </p>
841 *
842 * @param throwable
843 * the <code>Throwable</code> to be examined
844 * @return the stack trace as generated by the exception's
845 * <code>printStackTrace(PrintWriter)</code> method
846 */
847 public static String getStackTrace( Throwable throwable )
848 {
849 StringWriter sw = new StringWriter();
850 PrintWriter pw = new PrintWriter( sw, true );
851 throwable.printStackTrace( pw );
852 return sw.getBuffer().toString();
853 }
854
855
856 /**
857 * <p>
858 * A way to get the entire nested stack-trace of an throwable.
859 * </p>
860 *
861 * @param throwable
862 * the <code>Throwable</code> to be examined
863 * @return the nested stack trace, with the root cause first
864 * @since 2.0
865 */
866 public static String getFullStackTrace( Throwable throwable )
867 {
868 StringWriter sw = new StringWriter();
869 PrintWriter pw = new PrintWriter( sw, true );
870 Throwable[] ts = getThrowables( throwable );
871 for ( int i = 0; i < ts.length; i++ )
872 {
873 ts[i].printStackTrace( pw );
874 if ( isNestedThrowable( ts[i] ) )
875 {
876 break;
877 }
878 }
879 return sw.getBuffer().toString();
880 }
881
882
883 // -----------------------------------------------------------------------
884 /**
885 * <p>
886 * Captures the stack trace associated with the specified
887 * <code>Throwable</code> object, decomposing it into a list of stack
888 * frames.
889 * </p>
890 *
891 * @param throwable
892 * the <code>Throwable</code> to examine, may be null
893 * @return an array of strings describing each stack frame, never null
894 */
895 public static String[] getStackFrames( Throwable throwable )
896 {
897 if ( throwable == null )
898 {
899 return ArrayUtils.EMPTY_STRING_ARRAY;
900 }
901 return getStackFrames( getStackTrace( throwable ) );
902 }
903
904
905 /**
906 * <p>
907 * Functionality shared between the <code>getStackFrames(Throwable)</code>
908 * methods of this and the
909 */
910 static String[] getStackFrames( String stackTrace )
911 {
912 String linebreak = SystemUtils.LINE_SEPARATOR;
913 StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
914 List<String> list = new LinkedList<String>();
915
916 while ( frames.hasMoreTokens() )
917 {
918 list.add( frames.nextToken() );
919 }
920
921 return list.toArray( new String[list.size()] );
922 }
923
924
925 /**
926 * <p>
927 * Produces a <code>List</code> of stack frames - the message is not
928 * included.
929 * </p>
930 * <p/>
931 * <p>
932 * This works in most cases - it will only fail if the exception message
933 * contains a line that starts with:
934 * <code>" at".</code>
935 * </p>
936 *
937 * @param t
938 * is any throwable
939 * @return List of stack frames
940 */
941 static List<String> getStackFrameList( Throwable t )
942 {
943 String stackTrace = getStackTrace( t );
944 String linebreak = SystemUtils.LINE_SEPARATOR;
945 StringTokenizer frames = new StringTokenizer( stackTrace, linebreak );
946 List<String> list = new LinkedList<String>();
947 boolean traceStarted = false;
948
949 while ( frames.hasMoreTokens() )
950 {
951 String token = frames.nextToken();
952 // Determine if the line starts with <whitespace>at
953 int at = token.indexOf( "at" );
954
955 if ( at != -1 && token.substring( 0, at ).trim().length() == 0 )
956 {
957 traceStarted = true;
958 list.add( token );
959 }
960 else if ( traceStarted )
961 {
962 break;
963 }
964 }
965 return list;
966 }
967 }