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 }