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