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.lang3.exception; 018 019 import java.io.PrintStream; 020 import java.io.PrintWriter; 021 import java.io.StringWriter; 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.util.ArrayList; 025 import java.util.List; 026 import java.util.StringTokenizer; 027 028 import org.apache.commons.lang3.ArrayUtils; 029 import org.apache.commons.lang3.ClassUtils; 030 import org.apache.commons.lang3.StringUtils; 031 import org.apache.commons.lang3.SystemUtils; 032 033 /** 034 * <p>Provides utilities for manipulating and examining 035 * <code>Throwable</code> objects.</p> 036 * 037 * @since 1.0 038 * @version $Id: ExceptionUtils.java 1144929 2011-07-10 18:26:16Z ggregory $ 039 */ 040 public class ExceptionUtils { 041 042 /** 043 * <p>Used when printing stack frames to denote the start of a 044 * wrapped exception.</p> 045 * 046 * <p>Package private for accessibility by test suite.</p> 047 */ 048 static final String WRAPPED_MARKER = " [wrapped] "; 049 050 /** 051 * <p>The names of methods commonly used to access a wrapped exception.</p> 052 */ 053 // TODO: Remove in Lang 4.0 054 private static final String[] CAUSE_METHOD_NAMES = { 055 "getCause", 056 "getNextException", 057 "getTargetException", 058 "getException", 059 "getSourceException", 060 "getRootCause", 061 "getCausedByException", 062 "getNested", 063 "getLinkedException", 064 "getNestedException", 065 "getLinkedCause", 066 "getThrowable", 067 }; 068 069 /** 070 * <p> 071 * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not 072 * normally necessary. 073 * </p> 074 */ 075 public ExceptionUtils() { 076 super(); 077 } 078 079 //----------------------------------------------------------------------- 080 /** 081 * <p>Returns the default names used when searching for the cause of an exception.</p> 082 * 083 * <p>This may be modified and used in the overloaded getCause(Throwable, String[]) method.</p> 084 * 085 * @return cloned array of the default method names 086 * @since 3.0 087 * @deprecated This feature will be removed in Lang 4.0 088 */ 089 @Deprecated 090 public static String[] getDefaultCauseMethodNames() { 091 return ArrayUtils.clone(CAUSE_METHOD_NAMES); 092 } 093 094 //----------------------------------------------------------------------- 095 /** 096 * <p>Introspects the <code>Throwable</code> to obtain the cause.</p> 097 * 098 * <p>The method searches for methods with specific names that return a 099 * <code>Throwable</code> object. This will pick up most wrapping exceptions, 100 * including those from JDK 1.4. 101 * 102 * <p>The default list searched for are:</p> 103 * <ul> 104 * <li><code>getCause()</code></li> 105 * <li><code>getNextException()</code></li> 106 * <li><code>getTargetException()</code></li> 107 * <li><code>getException()</code></li> 108 * <li><code>getSourceException()</code></li> 109 * <li><code>getRootCause()</code></li> 110 * <li><code>getCausedByException()</code></li> 111 * <li><code>getNested()</code></li> 112 * </ul> 113 * 114 * <p>If none of the above is found, returns <code>null</code>.</p> 115 * 116 * @param throwable the throwable to introspect for a cause, may be null 117 * @return the cause of the <code>Throwable</code>, 118 * <code>null</code> if none found or null throwable input 119 * @since 1.0 120 * @deprecated This feature will be removed in Lang 4.0 121 */ 122 @Deprecated 123 public static Throwable getCause(Throwable throwable) { 124 return getCause(throwable, CAUSE_METHOD_NAMES); 125 } 126 127 /** 128 * <p>Introspects the <code>Throwable</code> to obtain the cause.</p> 129 * 130 * <p>A <code>null</code> set of method names means use the default set. 131 * A <code>null</code> in the set of method names will be ignored.</p> 132 * 133 * @param throwable the throwable to introspect for a cause, may be null 134 * @param methodNames the method names, null treated as default set 135 * @return the cause of the <code>Throwable</code>, 136 * <code>null</code> if none found or null throwable input 137 * @since 1.0 138 * @deprecated This feature will be removed in Lang 4.0 139 */ 140 @Deprecated 141 public static Throwable getCause(Throwable throwable, String[] methodNames) { 142 if (throwable == null) { 143 return null; 144 } 145 146 if (methodNames == null) { 147 methodNames = CAUSE_METHOD_NAMES; 148 } 149 150 for (String methodName : methodNames) { 151 if (methodName != null) { 152 Throwable cause = getCauseUsingMethodName(throwable, methodName); 153 if (cause != null) { 154 return cause; 155 } 156 } 157 } 158 159 return null; 160 } 161 162 /** 163 * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p> 164 * 165 * <p>This method walks through the exception chain to the last element, 166 * "root" of the tree, using {@link #getCause(Throwable)}, and 167 * returns that exception.</p> 168 * 169 * <p>From version 2.2, this method handles recursive cause structures 170 * that might otherwise cause infinite loops. If the throwable parameter 171 * has a cause of itself, then null will be returned. If the throwable 172 * parameter cause chain loops, the last element in the chain before the 173 * loop is returned.</p> 174 * 175 * @param throwable the throwable to get the root cause for, may be null 176 * @return the root cause of the <code>Throwable</code>, 177 * <code>null</code> if none found or null throwable input 178 */ 179 public static Throwable getRootCause(Throwable throwable) { 180 List<Throwable> list = getThrowableList(throwable); 181 return (list.size() < 2 ? null : (Throwable)list.get(list.size() - 1)); 182 } 183 184 /** 185 * <p>Finds a <code>Throwable</code> by method name.</p> 186 * 187 * @param throwable the exception to examine 188 * @param methodName the name of the method to find and invoke 189 * @return the wrapped exception, or <code>null</code> if not found 190 */ 191 // TODO: Remove in Lang 4.0 192 private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) { 193 Method method = null; 194 try { 195 method = throwable.getClass().getMethod(methodName); 196 } catch (NoSuchMethodException ignored) { // NOPMD 197 // exception ignored 198 } catch (SecurityException ignored) { // NOPMD 199 // exception ignored 200 } 201 202 if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { 203 try { 204 return (Throwable) method.invoke(throwable); 205 } catch (IllegalAccessException ignored) { // NOPMD 206 // exception ignored 207 } catch (IllegalArgumentException ignored) { // NOPMD 208 // exception ignored 209 } catch (InvocationTargetException ignored) { // NOPMD 210 // exception ignored 211 } 212 } 213 return null; 214 } 215 216 //----------------------------------------------------------------------- 217 /** 218 * <p>Counts the number of <code>Throwable</code> objects in the 219 * exception chain.</p> 220 * 221 * <p>A throwable without cause will return <code>1</code>. 222 * A throwable with one cause will return <code>2</code> and so on. 223 * A <code>null</code> throwable will return <code>0</code>.</p> 224 * 225 * <p>From version 2.2, this method handles recursive cause structures 226 * that might otherwise cause infinite loops. The cause chain is 227 * processed until the end is reached, or until the next item in the 228 * chain is already in the result set.</p> 229 * 230 * @param throwable the throwable to inspect, may be null 231 * @return the count of throwables, zero if null input 232 */ 233 public static int getThrowableCount(Throwable throwable) { 234 return getThrowableList(throwable).size(); 235 } 236 237 /** 238 * <p>Returns the list of <code>Throwable</code> objects in the 239 * exception chain.</p> 240 * 241 * <p>A throwable without cause will return an array containing 242 * one element - the input throwable. 243 * A throwable with one cause will return an array containing 244 * two elements. - the input throwable and the cause throwable. 245 * A <code>null</code> throwable will return an array of size zero.</p> 246 * 247 * <p>From version 2.2, this method handles recursive cause structures 248 * that might otherwise cause infinite loops. The cause chain is 249 * processed until the end is reached, or until the next item in the 250 * chain is already in the result set.</p> 251 * 252 * @see #getThrowableList(Throwable) 253 * @param throwable the throwable to inspect, may be null 254 * @return the array of throwables, never null 255 */ 256 public static Throwable[] getThrowables(Throwable throwable) { 257 List<Throwable> list = getThrowableList(throwable); 258 return list.toArray(new Throwable[list.size()]); 259 } 260 261 /** 262 * <p>Returns the list of <code>Throwable</code> objects in the 263 * exception chain.</p> 264 * 265 * <p>A throwable without cause will return a list containing 266 * one element - the input throwable. 267 * A throwable with one cause will return a list containing 268 * two elements. - the input throwable and the cause throwable. 269 * A <code>null</code> throwable will return a list of size zero.</p> 270 * 271 * <p>This method handles recursive cause structures that might 272 * otherwise cause infinite loops. The cause chain is processed until 273 * the end is reached, or until the next item in the chain is already 274 * in the result set.</p> 275 * 276 * @param throwable the throwable to inspect, may be null 277 * @return the list of throwables, never null 278 * @since Commons Lang 2.2 279 */ 280 public static List<Throwable> getThrowableList(Throwable throwable) { 281 List<Throwable> list = new ArrayList<Throwable>(); 282 while (throwable != null && list.contains(throwable) == false) { 283 list.add(throwable); 284 throwable = ExceptionUtils.getCause(throwable); 285 } 286 return list; 287 } 288 289 //----------------------------------------------------------------------- 290 /** 291 * <p>Returns the (zero based) index of the first <code>Throwable</code> 292 * that matches the specified class (exactly) in the exception chain. 293 * Subclasses of the specified class do not match - see 294 * {@link #indexOfType(Throwable, Class)} for the opposite.</p> 295 * 296 * <p>A <code>null</code> throwable returns <code>-1</code>. 297 * A <code>null</code> type returns <code>-1</code>. 298 * No match in the chain returns <code>-1</code>.</p> 299 * 300 * @param throwable the throwable to inspect, may be null 301 * @param clazz the class to search for, subclasses do not match, null returns -1 302 * @return the index into the throwable chain, -1 if no match or null input 303 */ 304 public static int indexOfThrowable(Throwable throwable, Class<?> clazz) { 305 return indexOf(throwable, clazz, 0, false); 306 } 307 308 /** 309 * <p>Returns the (zero based) index of the first <code>Throwable</code> 310 * that matches the specified type in the exception chain from 311 * a specified index. 312 * Subclasses of the specified class do not match - see 313 * {@link #indexOfType(Throwable, Class, int)} for the opposite.</p> 314 * 315 * <p>A <code>null</code> throwable returns <code>-1</code>. 316 * A <code>null</code> type returns <code>-1</code>. 317 * No match in the chain returns <code>-1</code>. 318 * A negative start index is treated as zero. 319 * A start index greater than the number of throwables returns <code>-1</code>.</p> 320 * 321 * @param throwable the throwable to inspect, may be null 322 * @param clazz the class to search for, subclasses do not match, null returns -1 323 * @param fromIndex the (zero based) index of the starting position, 324 * negative treated as zero, larger than chain size returns -1 325 * @return the index into the throwable chain, -1 if no match or null input 326 */ 327 public static int indexOfThrowable(Throwable throwable, Class<?> clazz, int fromIndex) { 328 return indexOf(throwable, clazz, fromIndex, false); 329 } 330 331 //----------------------------------------------------------------------- 332 /** 333 * <p>Returns the (zero based) index of the first <code>Throwable</code> 334 * that matches the specified class or subclass in the exception chain. 335 * Subclasses of the specified class do match - see 336 * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p> 337 * 338 * <p>A <code>null</code> throwable returns <code>-1</code>. 339 * A <code>null</code> type returns <code>-1</code>. 340 * No match in the chain returns <code>-1</code>.</p> 341 * 342 * @param throwable the throwable to inspect, may be null 343 * @param type the type to search for, subclasses match, null returns -1 344 * @return the index into the throwable chain, -1 if no match or null input 345 * @since 2.1 346 */ 347 public static int indexOfType(Throwable throwable, Class<?> type) { 348 return indexOf(throwable, type, 0, true); 349 } 350 351 /** 352 * <p>Returns the (zero based) index of the first <code>Throwable</code> 353 * that matches the specified type in the exception chain from 354 * a specified index. 355 * Subclasses of the specified class do match - see 356 * {@link #indexOfThrowable(Throwable, Class)} for the opposite.</p> 357 * 358 * <p>A <code>null</code> throwable returns <code>-1</code>. 359 * A <code>null</code> type returns <code>-1</code>. 360 * No match in the chain returns <code>-1</code>. 361 * A negative start index is treated as zero. 362 * A start index greater than the number of throwables returns <code>-1</code>.</p> 363 * 364 * @param throwable the throwable to inspect, may be null 365 * @param type the type to search for, subclasses match, null returns -1 366 * @param fromIndex the (zero based) index of the starting position, 367 * negative treated as zero, larger than chain size returns -1 368 * @return the index into the throwable chain, -1 if no match or null input 369 * @since 2.1 370 */ 371 public static int indexOfType(Throwable throwable, Class<?> type, int fromIndex) { 372 return indexOf(throwable, type, fromIndex, true); 373 } 374 375 /** 376 * <p>Worker method for the <code>indexOfType</code> methods.</p> 377 * 378 * @param throwable the throwable to inspect, may be null 379 * @param type the type to search for, subclasses match, null returns -1 380 * @param fromIndex the (zero based) index of the starting position, 381 * negative treated as zero, larger than chain size returns -1 382 * @param subclass if <code>true</code>, compares with {@link Class#isAssignableFrom(Class)}, otherwise compares 383 * using references 384 * @return index of the <code>type</code> within throwables nested withing the specified <code>throwable</code> 385 */ 386 private static int indexOf(Throwable throwable, Class<?> type, int fromIndex, boolean subclass) { 387 if (throwable == null || type == null) { 388 return -1; 389 } 390 if (fromIndex < 0) { 391 fromIndex = 0; 392 } 393 Throwable[] throwables = ExceptionUtils.getThrowables(throwable); 394 if (fromIndex >= throwables.length) { 395 return -1; 396 } 397 if (subclass) { 398 for (int i = fromIndex; i < throwables.length; i++) { 399 if (type.isAssignableFrom(throwables[i].getClass())) { 400 return i; 401 } 402 } 403 } else { 404 for (int i = fromIndex; i < throwables.length; i++) { 405 if (type.equals(throwables[i].getClass())) { 406 return i; 407 } 408 } 409 } 410 return -1; 411 } 412 413 //----------------------------------------------------------------------- 414 /** 415 * <p>Prints a compact stack trace for the root cause of a throwable 416 * to <code>System.err</code>.</p> 417 * 418 * <p>The compact stack trace starts with the root cause and prints 419 * stack frames up to the place where it was caught and wrapped. 420 * Then it prints the wrapped exception and continues with stack frames 421 * until the wrapper exception is caught and wrapped again, etc.</p> 422 * 423 * <p>The output of this method is consistent across JDK versions. 424 * Note that this is the opposite order to the JDK1.4 display.</p> 425 * 426 * <p>The method is equivalent to <code>printStackTrace</code> for throwables 427 * that don't have nested causes.</p> 428 * 429 * @param throwable the throwable to output 430 * @since 2.0 431 */ 432 public static void printRootCauseStackTrace(Throwable throwable) { 433 printRootCauseStackTrace(throwable, System.err); 434 } 435 436 /** 437 * <p>Prints a compact stack trace for the root cause of a throwable.</p> 438 * 439 * <p>The compact stack trace starts with the root cause and prints 440 * stack frames up to the place where it was caught and wrapped. 441 * Then it prints the wrapped exception and continues with stack frames 442 * until the wrapper exception is caught and wrapped again, etc.</p> 443 * 444 * <p>The output of this method is consistent across JDK versions. 445 * Note that this is the opposite order to the JDK1.4 display.</p> 446 * 447 * <p>The method is equivalent to <code>printStackTrace</code> for throwables 448 * that don't have nested causes.</p> 449 * 450 * @param throwable the throwable to output, may be null 451 * @param stream the stream to output to, may not be null 452 * @throws IllegalArgumentException if the stream is <code>null</code> 453 * @since 2.0 454 */ 455 public static void printRootCauseStackTrace(Throwable throwable, PrintStream stream) { 456 if (throwable == null) { 457 return; 458 } 459 if (stream == null) { 460 throw new IllegalArgumentException("The PrintStream must not be null"); 461 } 462 String trace[] = getRootCauseStackTrace(throwable); 463 for (String element : trace) { 464 stream.println(element); 465 } 466 stream.flush(); 467 } 468 469 /** 470 * <p>Prints a compact stack trace for the root cause of a throwable.</p> 471 * 472 * <p>The compact stack trace starts with the root cause and prints 473 * stack frames up to the place where it was caught and wrapped. 474 * Then it prints the wrapped exception and continues with stack frames 475 * until the wrapper exception is caught and wrapped again, etc.</p> 476 * 477 * <p>The output of this method is consistent across JDK versions. 478 * Note that this is the opposite order to the JDK1.4 display.</p> 479 * 480 * <p>The method is equivalent to <code>printStackTrace</code> for throwables 481 * that don't have nested causes.</p> 482 * 483 * @param throwable the throwable to output, may be null 484 * @param writer the writer to output to, may not be null 485 * @throws IllegalArgumentException if the writer is <code>null</code> 486 * @since 2.0 487 */ 488 public static void printRootCauseStackTrace(Throwable throwable, PrintWriter writer) { 489 if (throwable == null) { 490 return; 491 } 492 if (writer == null) { 493 throw new IllegalArgumentException("The PrintWriter must not be null"); 494 } 495 String trace[] = getRootCauseStackTrace(throwable); 496 for (String element : trace) { 497 writer.println(element); 498 } 499 writer.flush(); 500 } 501 502 //----------------------------------------------------------------------- 503 /** 504 * <p>Creates a compact stack trace for the root cause of the supplied 505 * <code>Throwable</code>.</p> 506 * 507 * <p>The output of this method is consistent across JDK versions. 508 * It consists of the root exception followed by each of its wrapping 509 * exceptions separated by '[wrapped]'. Note that this is the opposite 510 * order to the JDK1.4 display.</p> 511 * 512 * @param throwable the throwable to examine, may be null 513 * @return an array of stack trace frames, never null 514 * @since 2.0 515 */ 516 public static String[] getRootCauseStackTrace(Throwable throwable) { 517 if (throwable == null) { 518 return ArrayUtils.EMPTY_STRING_ARRAY; 519 } 520 Throwable throwables[] = getThrowables(throwable); 521 int count = throwables.length; 522 List<String> frames = new ArrayList<String>(); 523 List<String> nextTrace = getStackFrameList(throwables[count - 1]); 524 for (int i = count; --i >= 0;) { 525 List<String> trace = nextTrace; 526 if (i != 0) { 527 nextTrace = getStackFrameList(throwables[i - 1]); 528 removeCommonFrames(trace, nextTrace); 529 } 530 if (i == count - 1) { 531 frames.add(throwables[i].toString()); 532 } else { 533 frames.add(WRAPPED_MARKER + throwables[i].toString()); 534 } 535 for (int j = 0; j < trace.size(); j++) { 536 frames.add(trace.get(j)); 537 } 538 } 539 return frames.toArray(new String[frames.size()]); 540 } 541 542 /** 543 * <p>Removes common frames from the cause trace given the two stack traces.</p> 544 * 545 * @param causeFrames stack trace of a cause throwable 546 * @param wrapperFrames stack trace of a wrapper throwable 547 * @throws IllegalArgumentException if either argument is null 548 * @since 2.0 549 */ 550 public static void removeCommonFrames(List<String> causeFrames, List<String> wrapperFrames) { 551 if (causeFrames == null || wrapperFrames == null) { 552 throw new IllegalArgumentException("The List must not be null"); 553 } 554 int causeFrameIndex = causeFrames.size() - 1; 555 int wrapperFrameIndex = wrapperFrames.size() - 1; 556 while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { 557 // Remove the frame from the cause trace if it is the same 558 // as in the wrapper trace 559 String causeFrame = causeFrames.get(causeFrameIndex); 560 String wrapperFrame = wrapperFrames.get(wrapperFrameIndex); 561 if (causeFrame.equals(wrapperFrame)) { 562 causeFrames.remove(causeFrameIndex); 563 } 564 causeFrameIndex--; 565 wrapperFrameIndex--; 566 } 567 } 568 569 //----------------------------------------------------------------------- 570 /** 571 * <p>Gets the stack trace from a Throwable as a String.</p> 572 * 573 * <p>The result of this method vary by JDK version as this method 574 * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. 575 * On JDK1.3 and earlier, the cause exception will not be shown 576 * unless the specified throwable alters printStackTrace.</p> 577 * 578 * @param throwable the <code>Throwable</code> to be examined 579 * @return the stack trace as generated by the exception's 580 * <code>printStackTrace(PrintWriter)</code> method 581 */ 582 public static String getStackTrace(Throwable throwable) { 583 StringWriter sw = new StringWriter(); 584 PrintWriter pw = new PrintWriter(sw, true); 585 throwable.printStackTrace(pw); 586 return sw.getBuffer().toString(); 587 } 588 589 /** 590 * <p>Captures the stack trace associated with the specified 591 * <code>Throwable</code> object, decomposing it into a list of 592 * stack frames.</p> 593 * 594 * <p>The result of this method vary by JDK version as this method 595 * uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. 596 * On JDK1.3 and earlier, the cause exception will not be shown 597 * unless the specified throwable alters printStackTrace.</p> 598 * 599 * @param throwable the <code>Throwable</code> to examine, may be null 600 * @return an array of strings describing each stack frame, never null 601 */ 602 public static String[] getStackFrames(Throwable throwable) { 603 if (throwable == null) { 604 return ArrayUtils.EMPTY_STRING_ARRAY; 605 } 606 return getStackFrames(getStackTrace(throwable)); 607 } 608 609 //----------------------------------------------------------------------- 610 /** 611 * <p>Returns an array where each element is a line from the argument.</p> 612 * 613 * <p>The end of line is determined by the value of {@link SystemUtils#LINE_SEPARATOR}.</p> 614 * 615 * @param stackTrace a stack trace String 616 * @return an array where each element is a line from the argument 617 */ 618 static String[] getStackFrames(String stackTrace) { 619 String linebreak = SystemUtils.LINE_SEPARATOR; 620 StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); 621 List<String> list = new ArrayList<String>(); 622 while (frames.hasMoreTokens()) { 623 list.add(frames.nextToken()); 624 } 625 return list.toArray(new String[list.size()]); 626 } 627 628 /** 629 * <p>Produces a <code>List</code> of stack frames - the message 630 * is not included. Only the trace of the specified exception is 631 * returned, any caused by trace is stripped.</p> 632 * 633 * <p>This works in most cases - it will only fail if the exception 634 * message contains a line that starts with: 635 * <code>" at".</code></p> 636 * 637 * @param t is any throwable 638 * @return List of stack frames 639 */ 640 static List<String> getStackFrameList(Throwable t) { 641 String stackTrace = getStackTrace(t); 642 String linebreak = SystemUtils.LINE_SEPARATOR; 643 StringTokenizer frames = new StringTokenizer(stackTrace, linebreak); 644 List<String> list = new ArrayList<String>(); 645 boolean traceStarted = false; 646 while (frames.hasMoreTokens()) { 647 String token = frames.nextToken(); 648 // Determine if the line starts with <whitespace>at 649 int at = token.indexOf("at"); 650 if (at != -1 && token.substring(0, at).trim().length() == 0) { 651 traceStarted = true; 652 list.add(token); 653 } else if (traceStarted) { 654 break; 655 } 656 } 657 return list; 658 } 659 660 //----------------------------------------------------------------------- 661 /** 662 * Gets a short message summarising the exception. 663 * <p> 664 * The message returned is of the form 665 * {ClassNameWithoutPackage}: {ThrowableMessage} 666 * 667 * @param th the throwable to get a message for, null returns empty string 668 * @return the message, non-null 669 * @since Commons Lang 2.2 670 */ 671 public static String getMessage(Throwable th) { 672 if (th == null) { 673 return ""; 674 } 675 String clsName = ClassUtils.getShortClassName(th, null); 676 String msg = th.getMessage(); 677 return clsName + ": " + StringUtils.defaultString(msg); 678 } 679 680 //----------------------------------------------------------------------- 681 /** 682 * Gets a short message summarising the root cause exception. 683 * <p> 684 * The message returned is of the form 685 * {ClassNameWithoutPackage}: {ThrowableMessage} 686 * 687 * @param th the throwable to get a message for, null returns empty string 688 * @return the message, non-null 689 * @since Commons Lang 2.2 690 */ 691 public static String getRootCauseMessage(Throwable th) { 692 Throwable root = ExceptionUtils.getRootCause(th); 693 root = (root == null ? th : root); 694 return getMessage(root); 695 } 696 697 }