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 }