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