1. /* ====================================================================
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowledgement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowledgement may appear in the software itself,
  24. * if and wherever such third-party acknowledgements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Commons", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Software Foundation.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.commons.lang.exception;
  55. import java.io.PrintStream;
  56. import java.io.PrintWriter;
  57. import java.io.StringWriter;
  58. import java.lang.reflect.Field;
  59. import java.lang.reflect.InvocationTargetException;
  60. import java.lang.reflect.Method;
  61. import java.sql.SQLException;
  62. import java.util.ArrayList;
  63. import java.util.Arrays;
  64. import java.util.LinkedList;
  65. import java.util.List;
  66. import java.util.StringTokenizer;
  67. import org.apache.commons.lang.ArrayUtils;
  68. import org.apache.commons.lang.StringUtils;
  69. import org.apache.commons.lang.SystemUtils;
  70. /**
  71. * <p>Provides utilities for manipulating and examining
  72. * <code>Throwable</code> objects.</p>
  73. *
  74. * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
  75. * @author Dmitri Plotnikov
  76. * @author Stephen Colebourne
  77. * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
  78. * @author Pete Gieser
  79. * @since 1.0
  80. * @version $Id: ExceptionUtils.java,v 1.34 2003/08/21 15:52:55 ggregory Exp $
  81. */
  82. public class ExceptionUtils {
  83. /**
  84. * <p>Used when printing stack frames to denote the start of a
  85. * wrapped exception.</p>
  86. *
  87. * <p>Package private for accessibility by test suite.</p>
  88. */
  89. static final String WRAPPED_MARKER = " [wrapped] ";
  90. /**
  91. * <p>The names of methods commonly used to access a wrapped exception.</p>
  92. */
  93. private static String[] CAUSE_METHOD_NAMES = {
  94. "getCause",
  95. "getNextException",
  96. "getTargetException",
  97. "getException",
  98. "getSourceException",
  99. "getRootCause",
  100. "getCausedByException",
  101. "getNested"
  102. };
  103. /**
  104. * <p>The Method object for JDK1.4 getCause.</p>
  105. */
  106. private static final Method THROWABLE_CAUSE_METHOD;
  107. static {
  108. Method getCauseMethod;
  109. try {
  110. getCauseMethod = Throwable.class.getMethod("getCause", null);
  111. } catch (Exception e) {
  112. getCauseMethod = null;
  113. }
  114. THROWABLE_CAUSE_METHOD = getCauseMethod;
  115. }
  116. /**
  117. * <p>Public constructor allows an instance of <code>ExceptionUtils</code>
  118. * to be created, although that is not normally necessary.</p>
  119. */
  120. public ExceptionUtils() {
  121. }
  122. //-----------------------------------------------------------------------
  123. /**
  124. * <p>Adds to the list of method names used in the search for <code>Throwable</code>
  125. * objects.</p>
  126. *
  127. * @param methodName the methodName to add to the list, <code>null</code>
  128. * and empty strings are ignored
  129. * @since 2.0
  130. */
  131. public static void addCauseMethodName(String methodName) {
  132. if (StringUtils.isNotEmpty(methodName)) {
  133. List list = new ArrayList(Arrays.asList(CAUSE_METHOD_NAMES));
  134. list.add(methodName);
  135. CAUSE_METHOD_NAMES = (String[]) list.toArray(new String[list.size()]);
  136. }
  137. }
  138. /**
  139. * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
  140. *
  141. * <p>The method searches for methods with specific names that return a
  142. * <code>Throwable</code> object. This will pick up most wrapping exceptions,
  143. * including those from JDK 1.4, and
  144. * {@link org.apache.commons.lang.exception.NestableException NestableException}.
  145. * The method names can be added to using {@link #addCauseMethodName(String)}.</p>
  146. *
  147. * <p>The default list searched for are:</p>
  148. * <ul>
  149. * <li><code>getCause()</code></li>
  150. * <li><code>getNextException()</code></li>
  151. * <li><code>getTargetException()</code></li>
  152. * <li><code>getException()</code></li>
  153. * <li><code>getSourceException()</code></li>
  154. * <li><code>getRootCause()</code></li>
  155. * <li><code>getCausedByException()</code></li>
  156. * <li><code>getNested()</code></li>
  157. * </ul>
  158. *
  159. * <p>In the absence of any such method, the object is inspected for a
  160. * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
  161. *
  162. * <p>If none of the above is found, returns <code>null</code>.</p>
  163. *
  164. * @param throwable the throwable to introspect for a cause, may be null
  165. * @return the cause of the <code>Throwable</code>,
  166. * <code>null</code> if none found or null throwable input
  167. */
  168. public static Throwable getCause(Throwable throwable) {
  169. return getCause(throwable, CAUSE_METHOD_NAMES);
  170. }
  171. /**
  172. * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
  173. *
  174. * <ol>
  175. * <li>Try known exception types.</li>
  176. * <li>Try the supplied array of method names.</li>
  177. * <li>Try the field 'detail'.</li>
  178. * </ol>
  179. *
  180. * <p>A <code>null</code> set of method names means use the default set.
  181. * A <code>null</code> in the set of method names will be ignored.</p>
  182. *
  183. * @param throwable the throwable to introspect for a cause, may be null
  184. * @param methodNames the method names, null treated as default set
  185. * @return the cause of the <code>Throwable</code>,
  186. * <code>null</code> if none found or null throwable input
  187. */
  188. public static Throwable getCause(Throwable throwable, String[] methodNames) {
  189. if (throwable == null) {
  190. return null;
  191. }
  192. Throwable cause = getCauseUsingWellKnownTypes(throwable);
  193. if (cause == null) {
  194. if (methodNames == null) {
  195. methodNames = CAUSE_METHOD_NAMES;
  196. }
  197. for (int i = 0; i < methodNames.length; i++) {
  198. String methodName = methodNames[i];
  199. if (methodName != null) {
  200. cause = getCauseUsingMethodName(throwable, methodName);
  201. if (cause != null) {
  202. break;
  203. }
  204. }
  205. }
  206. if (cause == null) {
  207. cause = getCauseUsingFieldName(throwable, "detail");
  208. }
  209. }
  210. return cause;
  211. }
  212. /**
  213. * <p>Introspects the <code>Throwable</code> to obtain the root cause.</p>
  214. *
  215. * <p>This method walks through the exception chain to the last element,
  216. * "root" of the tree, using {@link #getCause(Throwable)}, and
  217. * returns that exception.</p>
  218. *
  219. * @param throwable the throwable to get the root cause for, may be null
  220. * @return the root cause of the <code>Throwable</code>,
  221. * <code>null</code> if none found or null throwable input
  222. */
  223. public static Throwable getRootCause(Throwable throwable) {
  224. Throwable cause = getCause(throwable);
  225. if (cause != null) {
  226. throwable = cause;
  227. while ((throwable = getCause(throwable)) != null) {
  228. cause = throwable;
  229. }
  230. }
  231. return cause;
  232. }
  233. /**
  234. * <p>Finds a <code>Throwable</code> for known types.</p>
  235. *
  236. * <p>Uses <code>instanceof</code> checks to examine the exception,
  237. * looking for well known types which could contain chained or
  238. * wrapped exceptions.</p>
  239. *
  240. * @param throwable the exception to examine
  241. * @return the wrapped exception, or <code>null</code> if not found
  242. */
  243. private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
  244. if (throwable instanceof Nestable) {
  245. return ((Nestable) throwable).getCause();
  246. } else if (throwable instanceof SQLException) {
  247. return ((SQLException) throwable).getNextException();
  248. } else if (throwable instanceof InvocationTargetException) {
  249. return ((InvocationTargetException) throwable).getTargetException();
  250. } else {
  251. return null;
  252. }
  253. }
  254. /**
  255. * <p>Finds a <code>Throwable</code> by method name.</p>
  256. *
  257. * @param throwable the exception to examine
  258. * @param methodName the name of the method to find and invoke
  259. * @return the wrapped exception, or <code>null</code> if not found
  260. */
  261. private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
  262. Method method = null;
  263. try {
  264. method = throwable.getClass().getMethod(methodName, null);
  265. } catch (NoSuchMethodException ignored) {
  266. } catch (SecurityException ignored) {
  267. }
  268. if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
  269. try {
  270. return (Throwable) method.invoke(throwable, ArrayUtils.EMPTY_OBJECT_ARRAY);
  271. } catch (IllegalAccessException ignored) {
  272. } catch (IllegalArgumentException ignored) {
  273. } catch (InvocationTargetException ignored) {
  274. }
  275. }
  276. return null;
  277. }
  278. /**
  279. * <p>Finds a <code>Throwable</code> by field name.</p>
  280. *
  281. * @param throwable the exception to examine
  282. * @param fieldName the name of the attribute to examine
  283. * @return the wrapped exception, or <code>null</code> if not found
  284. */
  285. private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
  286. Field field = null;
  287. try {
  288. field = throwable.getClass().getField(fieldName);
  289. } catch (NoSuchFieldException ignored) {
  290. } catch (SecurityException ignored) {
  291. }
  292. if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
  293. try {
  294. return (Throwable) field.get(throwable);
  295. } catch (IllegalAccessException ignored) {
  296. } catch (IllegalArgumentException ignored) {
  297. }
  298. }
  299. return null;
  300. }
  301. //-----------------------------------------------------------------------
  302. /**
  303. * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
  304. *
  305. * <p>This is true for JDK 1.4 and above.</p>
  306. *
  307. * @return true if Throwable is nestable
  308. * @since 2.0
  309. */
  310. public static boolean isThrowableNested() {
  311. return (THROWABLE_CAUSE_METHOD != null);
  312. }
  313. /**
  314. * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
  315. *
  316. * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
  317. *
  318. * @param throwable the <code>Throwable</code> to examine, may be null
  319. * @return boolean <code>true</code> if nested otherwise <code>false</code>
  320. * @since 2.0
  321. */
  322. public static boolean isNestedThrowable(Throwable throwable) {
  323. if (throwable == null) {
  324. return false;
  325. }
  326. if (throwable instanceof Nestable) {
  327. return true;
  328. } else if (throwable instanceof SQLException) {
  329. return true;
  330. } else if (throwable instanceof InvocationTargetException) {
  331. return true;
  332. } else if (isThrowableNested()) {
  333. return true;
  334. }
  335. Class cls = throwable.getClass();
  336. for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
  337. try {
  338. Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], null);
  339. if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
  340. return true;
  341. }
  342. } catch (NoSuchMethodException ignored) {
  343. } catch (SecurityException ignored) {
  344. }
  345. }
  346. try {
  347. Field field = cls.getField("detail");
  348. if (field != null) {
  349. return true;
  350. }
  351. } catch (NoSuchFieldException ignored) {
  352. } catch (SecurityException ignored) {
  353. }
  354. return false;
  355. }
  356. //-----------------------------------------------------------------------
  357. /**
  358. * <p>Counts the number of <code>Throwable</code> objects in the
  359. * exception chain.</p>
  360. *
  361. * <p>A throwable without cause will return <code>1</code>.
  362. * A throwable with one cause will return <code>2</code> and so on.
  363. * A <code>null</code> throwable will return <code>0</code>.</p>
  364. *
  365. * @param throwable the throwable to inspect, may be null
  366. * @return the count of throwables, zero if null input
  367. */
  368. public static int getThrowableCount(Throwable throwable) {
  369. int count = 0;
  370. while (throwable != null) {
  371. count++;
  372. throwable = ExceptionUtils.getCause(throwable);
  373. }
  374. return count;
  375. }
  376. /**
  377. * <p>Returns the list of <code>Throwable</code> objects in the
  378. * exception chain.</p>
  379. *
  380. * <p>A throwable without cause will return an array containing
  381. * one element - the input throwable.
  382. * A throwable with one cause will return an array containing
  383. * two elements. - the input throwable and the cause throwable.
  384. * A <code>null</code> throwable will return an array size zero.</p>
  385. *
  386. * @param throwable the throwable to inspect, may be null
  387. * @return the array of throwables, never null
  388. */
  389. public static Throwable[] getThrowables(Throwable throwable) {
  390. List list = new ArrayList();
  391. while (throwable != null) {
  392. list.add(throwable);
  393. throwable = ExceptionUtils.getCause(throwable);
  394. }
  395. return (Throwable[]) list.toArray(new Throwable[list.size()]);
  396. }
  397. //-----------------------------------------------------------------------
  398. /**
  399. * <p>Returns the (zero based) index of the first <code>Throwable</code>
  400. * that matches the specified type in the exception chain.</p>
  401. *
  402. * <p>A <code>null</code> throwable returns <code>-1</code>.
  403. * A <code>null</code> type returns <code>-1</code>.
  404. * No match in the chain returns <code>-1</code>.</p>
  405. *
  406. * @param throwable the throwable to inspect, may be null
  407. * @param type the type to search for
  408. * @return the index into the throwable chain, -1 if no match or null input
  409. */
  410. public static int indexOfThrowable(Throwable throwable, Class type) {
  411. return indexOfThrowable(throwable, type, 0);
  412. }
  413. /**
  414. * <p>Returns the (zero based) index of the first <code>Throwable</code>
  415. * that matches the specified type in the exception chain from
  416. * a specified index.</p>
  417. *
  418. * <p>A <code>null</code> throwable returns <code>-1</code>.
  419. * A <code>null</code> type returns <code>-1</code>.
  420. * No match in the chain returns <code>-1</code>.
  421. * A negative start index is treated as zero.
  422. * A start index greater than the number of throwables returns <code>-1</code>.</p>
  423. *
  424. * @param throwable the throwable to inspect, may be null
  425. * @param type the type to search for
  426. * @param fromIndex the (zero based) index of the starting position,
  427. * negative treated as zero, larger than chain size returns -1
  428. * @return the index into the throwable chain, -1 if no match or null input
  429. */
  430. public static int indexOfThrowable(Throwable throwable, Class type, int fromIndex) {
  431. if (throwable == null) {
  432. return -1;
  433. }
  434. if (fromIndex < 0) {
  435. fromIndex = 0;
  436. }
  437. Throwable[] throwables = ExceptionUtils.getThrowables(throwable);
  438. if (fromIndex >= throwables.length) {
  439. return -1;
  440. }
  441. for (int i = fromIndex; i < throwables.length; i++) {
  442. if (throwables[i].getClass().equals(type)) {
  443. return i;
  444. }
  445. }
  446. return -1;
  447. }
  448. //-----------------------------------------------------------------------
  449. /**
  450. * <p>Prints a compact stack trace for the root cause of a throwable
  451. * to <code>System.err</code>.</p>
  452. *
  453. * <p>The compact stack trace starts with the root cause and prints
  454. * stack frames up to the place where it was caught and wrapped.
  455. * Then it prints the wrapped exception and continues with stack frames
  456. * until the wrapper exception is caught and wrapped again, etc.</p>
  457. *
  458. * <p>The method is equivalent to <code>printStackTrace</code> for throwables
  459. * that don't have nested causes.</p>
  460. *
  461. * @param throwable the throwable to output
  462. * @since 2.0
  463. */
  464. public static void printRootCauseStackTrace(Throwable throwable) {
  465. printRootCauseStackTrace(throwable, System.err);
  466. }
  467. /**
  468. * <p>Prints a compact stack trace for the root cause of a throwable.</p>
  469. *
  470. * <p>The compact stack trace starts with the root cause and prints
  471. * stack frames up to the place where it was caught and wrapped.
  472. * Then it prints the wrapped exception and continues with stack frames
  473. * until the wrapper exception is caught and wrapped again, etc.</p>
  474. *
  475. * <p>The method is equivalent to <code>printStackTrace</code> for throwables
  476. * that don't have nested causes.</p>
  477. *
  478. * @param throwable the throwable to output, may be null
  479. * @param stream the stream to output to, may not be null
  480. * @throws IllegalArgumentException if the stream is <code>null</code>
  481. * @since 2.0
  482. */
  483. public static void printRootCauseStackTrace(Throwable throwable, PrintStream stream) {
  484. if (throwable == null) {
  485. return;
  486. }
  487. if (stream == null) {
  488. throw new IllegalArgumentException("The PrintStream must not be null");
  489. }
  490. String trace[] = getRootCauseStackTrace(throwable);
  491. for (int i = 0; i < trace.length; i++) {
  492. stream.println(trace[i]);
  493. }
  494. stream.flush();
  495. }
  496. /**
  497. * <p>Prints a compact stack trace for the root cause of a throwable.</p>
  498. *
  499. * <p>The compact stack trace starts with the root cause and prints
  500. * stack frames up to the place where it was caught and wrapped.
  501. * Then it prints the wrapped exception and continues with stack frames
  502. * until the wrapper exception is caught and wrapped again, etc.</p>
  503. *
  504. * <p>The method is equivalent to <code>printStackTrace</code> for throwables
  505. * that don't have nested causes.</p>
  506. *
  507. * @param throwable the throwable to output, may be null
  508. * @param writer the writer to output to, may not be null
  509. * @throws IllegalArgumentException if the writer is <code>null</code>
  510. * @since 2.0
  511. */
  512. public static void printRootCauseStackTrace(Throwable throwable, PrintWriter writer) {
  513. if (throwable == null) {
  514. return;
  515. }
  516. if (writer == null) {
  517. throw new IllegalArgumentException("The PrintWriter must not be null");
  518. }
  519. String trace[] = getRootCauseStackTrace(throwable);
  520. for (int i = 0; i < trace.length; i++) {
  521. writer.println(trace[i]);
  522. }
  523. writer.flush();
  524. }
  525. //-----------------------------------------------------------------------
  526. /**
  527. * <p>Creates a compact stack trace for the root cause of the supplied
  528. * <code>Throwable</code>.</p>
  529. *
  530. * @param throwable the throwable to examine, may be null
  531. * @return an array of stack trace frames, never null
  532. * @since 2.0
  533. */
  534. public static String[] getRootCauseStackTrace(Throwable throwable) {
  535. if (throwable == null) {
  536. return ArrayUtils.EMPTY_STRING_ARRAY;
  537. }
  538. Throwable throwables[] = getThrowables(throwable);
  539. int count = throwables.length;
  540. ArrayList frames = new ArrayList();
  541. List nextTrace = getStackFrameList(throwables[count - 1]);
  542. for (int i = count; --i >= 0;) {
  543. List trace = nextTrace;
  544. if (i != 0) {
  545. nextTrace = getStackFrameList(throwables[i - 1]);
  546. removeCommonFrames(trace, nextTrace);
  547. }
  548. if (i == count - 1) {
  549. frames.add(throwables[i].toString());
  550. } else {
  551. frames.add(WRAPPED_MARKER + throwables[i].toString());
  552. }
  553. for (int j = 0; j < trace.size(); j++) {
  554. frames.add(trace.get(j));
  555. }
  556. }
  557. return (String[]) frames.toArray(new String[0]);
  558. }
  559. /**
  560. * <p>Removes common frames from the cause trace given the two stack traces.</p>
  561. *
  562. * @param causeFrames stack trace of a cause throwable
  563. * @param wrapperFrames stack trace of a wrapper throwable
  564. * @throws IllegalArgumentException if either argument is null
  565. * @since 2.0
  566. */
  567. public static void removeCommonFrames(List causeFrames, List wrapperFrames) {
  568. if (causeFrames == null || wrapperFrames == null) {
  569. throw new IllegalArgumentException("The List must not be null");
  570. }
  571. int causeFrameIndex = causeFrames.size() - 1;
  572. int wrapperFrameIndex = wrapperFrames.size() - 1;
  573. while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) {
  574. // Remove the frame from the cause trace if it is the same
  575. // as in the wrapper trace
  576. String causeFrame = (String) causeFrames.get(causeFrameIndex);
  577. String wrapperFrame = (String) wrapperFrames.get(wrapperFrameIndex);
  578. if (causeFrame.equals(wrapperFrame)) {
  579. causeFrames.remove(causeFrameIndex);
  580. }
  581. causeFrameIndex--;
  582. wrapperFrameIndex--;
  583. }
  584. }
  585. //-----------------------------------------------------------------------
  586. /**
  587. * <p>Gets the stack trace from a Throwable as a String.</p>
  588. *
  589. * @param throwable the <code>Throwable</code> to be examined
  590. * @return the stack trace as generated by the exception's
  591. * <code>printStackTrace(PrintWriter)</code> method
  592. */
  593. public static String getStackTrace(Throwable throwable) {
  594. StringWriter sw = new StringWriter();
  595. PrintWriter pw = new PrintWriter(sw, true);
  596. throwable.printStackTrace(pw);
  597. return sw.getBuffer().toString();
  598. }
  599. /**
  600. * <p>A way to get the entire nested stack-trace of an throwable.</p>
  601. *
  602. * @param throwable the <code>Throwable</code> to be examined
  603. * @return the nested stack trace, with the root cause first
  604. * @since 2.0
  605. */
  606. public static String getFullStackTrace(Throwable throwable) {
  607. StringWriter sw = new StringWriter();
  608. PrintWriter pw = new PrintWriter(sw, true);
  609. Throwable[] ts = getThrowables(throwable);
  610. for (int i = 0; i < ts.length; i++) {
  611. ts[i].printStackTrace(pw);
  612. if (isNestedThrowable(ts[i])) {
  613. break;
  614. }
  615. }
  616. return sw.getBuffer().toString();
  617. }
  618. //-----------------------------------------------------------------------
  619. /**
  620. * <p>Captures the stack trace associated with the specified
  621. * <code>Throwable</code> object, decomposing it into a list of
  622. * stack frames.</p>
  623. *
  624. * @param throwable the <code>Throwable</code> to exaamine, may be null
  625. * @return an array of strings describing each stack frame, never null
  626. */
  627. public static String[] getStackFrames(Throwable throwable) {
  628. if (throwable == null) {
  629. return ArrayUtils.EMPTY_STRING_ARRAY;
  630. }
  631. return getStackFrames(getStackTrace(throwable));
  632. }
  633. /**
  634. * <p>Functionality shared between the
  635. * <code>getStackFrames(Throwable)</code> methods of this and the
  636. * {@link org.apache.commons.lang.exception.NestableDelegate}
  637. * classes.</p>
  638. */
  639. static String[] getStackFrames(String stackTrace) {
  640. String linebreak = SystemUtils.LINE_SEPARATOR;
  641. StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
  642. List list = new LinkedList();
  643. while (frames.hasMoreTokens()) {
  644. list.add(frames.nextToken());
  645. }
  646. return (String[]) list.toArray(new String[list.size()]);
  647. }
  648. /**
  649. * <p>Produces a <code>List</code> of stack frames - the message
  650. * is not included.</p>
  651. *
  652. * <p>This works in most cases - it will only fail if the exception
  653. * message contains a line that starts with:
  654. * <code>"   at".</code></p>
  655. *
  656. * @param t is any throwable
  657. * @return List of stack frames
  658. */
  659. static List getStackFrameList(Throwable t) {
  660. String stackTrace = getStackTrace(t);
  661. String linebreak = SystemUtils.LINE_SEPARATOR;
  662. StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
  663. List list = new LinkedList();
  664. boolean traceStarted = false;
  665. while (frames.hasMoreTokens()) {
  666. String token = frames.nextToken();
  667. // Determine if the line starts with <whitespace>at
  668. int at = token.indexOf("at");
  669. if (at != -1 && token.substring(0, at).trim().length() == 0) {
  670. traceStarted = true;
  671. list.add(token);
  672. } else if (traceStarted) {
  673. break;
  674. }
  675. }
  676. return list;
  677. }
  678. }