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.Serializable;
  58. import java.io.StringWriter;
  59. import java.util.ArrayList;
  60. import java.util.Arrays;
  61. import java.util.Collections;
  62. import java.util.Iterator;
  63. import java.util.List;
  64. /**
  65. * <p>A shared implementation of the nestable exception functionality.</p>
  66. * <p>
  67. * The code is shared between
  68. * {@link org.apache.commons.lang.exception.NestableError NestableError},
  69. * {@link org.apache.commons.lang.exception.NestableException NestableException} and
  70. * {@link org.apache.commons.lang.exception.NestableRuntimeException NestableRuntimeException}.
  71. * </p>
  72. *
  73. * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
  74. * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
  75. * @author <a href="mailto:knielsen@apache.org">Kasper Nielsen</a>
  76. * @author <a href="mailto:steven@caswell.name">Steven Caswell</a>
  77. * @author Sean C. Sullivan
  78. * @author Stephen Colebourne
  79. * @since 1.0
  80. * @version $Id: NestableDelegate.java,v 1.20 2003/08/23 00:41:29 ggregory Exp $
  81. */
  82. public class NestableDelegate implements Serializable {
  83. /**
  84. * Constructor error message.
  85. */
  86. private transient static final String MUST_BE_THROWABLE =
  87. "The Nestable implementation passed to the NestableDelegate(Nestable) "
  88. + "constructor must extend java.lang.Throwable";
  89. /**
  90. * Holds the reference to the exception or error that we're
  91. * wrapping (which must be a {@link
  92. * org.apache.commons.lang.exception.Nestable} implementation).
  93. */
  94. private Throwable nestable = null;
  95. /**
  96. * Whether to print the stack trace top-down.
  97. * This public flag may be set by calling code, typically in initialisation.
  98. * @since 2.0
  99. */
  100. public static boolean topDown = true;
  101. /**
  102. * Whether to trim the repeated stack trace.
  103. * This public flag may be set by calling code, typically in initialisation.
  104. * @since 2.0
  105. */
  106. public static boolean trimStackFrames = true;
  107. /**
  108. * Constructs a new <code>NestableDelegate</code> instance to manage the
  109. * specified <code>Nestable</code>.
  110. *
  111. * @param nestable the Nestable implementation (<i>must</i> extend
  112. * {@link java.lang.Throwable})
  113. * @since 2.0
  114. */
  115. public NestableDelegate(Nestable nestable) {
  116. if (nestable instanceof Throwable) {
  117. this.nestable = (Throwable) nestable;
  118. } else {
  119. throw new IllegalArgumentException(MUST_BE_THROWABLE);
  120. }
  121. }
  122. /**
  123. * Returns the error message of the <code>Throwable</code> in the chain
  124. * of <code>Throwable</code>s at the specified index, numbererd from 0.
  125. *
  126. * @param index the index of the <code>Throwable</code> in the chain of
  127. * <code>Throwable</code>s
  128. * @return the error message, or null if the <code>Throwable</code> at the
  129. * specified index in the chain does not contain a message
  130. * @throws IndexOutOfBoundsException if the <code>index</code> argument is
  131. * negative or not less than the count of <code>Throwable</code>s in the
  132. * chain
  133. * @since 2.0
  134. */
  135. public String getMessage(int index) {
  136. Throwable t = this.getThrowable(index);
  137. if (Nestable.class.isInstance(t)) {
  138. return ((Nestable) t).getMessage(0);
  139. } else {
  140. return t.getMessage();
  141. }
  142. }
  143. /**
  144. * Returns the full message contained by the <code>Nestable</code>
  145. * and any nested <code>Throwable</code>s.
  146. *
  147. * @param baseMsg the base message to use when creating the full
  148. * message. Should be generally be called via
  149. * <code>nestableHelper.getMessage(super.getMessage())</code>,
  150. * where <code>super</code> is an instance of {@link
  151. * java.lang.Throwable}.
  152. * @return The concatenated message for this and all nested
  153. * <code>Throwable</code>s
  154. * @since 2.0
  155. */
  156. public String getMessage(String baseMsg) {
  157. StringBuffer msg = new StringBuffer();
  158. if (baseMsg != null) {
  159. msg.append(baseMsg);
  160. }
  161. Throwable nestedCause = ExceptionUtils.getCause(this.nestable);
  162. if (nestedCause != null) {
  163. String causeMsg = nestedCause.getMessage();
  164. if (causeMsg != null) {
  165. if (baseMsg != null) {
  166. msg.append(": ");
  167. }
  168. msg.append(causeMsg);
  169. }
  170. }
  171. return (msg.length() > 0 ? msg.toString() : null);
  172. }
  173. /**
  174. * Returns the error message of this and any nested <code>Throwable</code>s
  175. * in an array of Strings, one element for each message. Any
  176. * <code>Throwable</code> not containing a message is represented in the
  177. * array by a null. This has the effect of cause the length of the returned
  178. * array to be equal to the result of the {@link #getThrowableCount()}
  179. * operation.
  180. *
  181. * @return the error messages
  182. * @since 2.0
  183. */
  184. public String[] getMessages() {
  185. Throwable[] throwables = this.getThrowables();
  186. String[] msgs = new String[throwables.length];
  187. for (int i = 0; i < throwables.length; i++) {
  188. msgs[i] =
  189. (Nestable.class.isInstance(throwables[i])
  190. ? ((Nestable) throwables[i]).getMessage(0)
  191. : throwables[i].getMessage());
  192. }
  193. return msgs;
  194. }
  195. /**
  196. * Returns the <code>Throwable</code> in the chain of
  197. * <code>Throwable</code>s at the specified index, numbererd from 0.
  198. *
  199. * @param index the index, numbered from 0, of the <code>Throwable</code> in
  200. * the chain of <code>Throwable</code>s
  201. * @return the <code>Throwable</code>
  202. * @throws IndexOutOfBoundsException if the <code>index</code> argument is
  203. * negative or not less than the count of <code>Throwable</code>s in the
  204. * chain
  205. * @since 2.0
  206. */
  207. public Throwable getThrowable(int index) {
  208. if (index == 0) {
  209. return this.nestable;
  210. }
  211. Throwable[] throwables = this.getThrowables();
  212. return throwables[index];
  213. }
  214. /**
  215. * Returns the number of <code>Throwable</code>s contained in the
  216. * <code>Nestable</code> contained by this delegate.
  217. *
  218. * @return the throwable count
  219. * @since 2.0
  220. */
  221. public int getThrowableCount() {
  222. return ExceptionUtils.getThrowableCount(this.nestable);
  223. }
  224. /**
  225. * Returns this delegate's <code>Nestable</code> and any nested
  226. * <code>Throwable</code>s in an array of <code>Throwable</code>s, one
  227. * element for each <code>Throwable</code>.
  228. *
  229. * @return the <code>Throwable</code>s
  230. * @since 2.0
  231. */
  232. public Throwable[] getThrowables() {
  233. return ExceptionUtils.getThrowables(this.nestable);
  234. }
  235. /**
  236. * Returns the index, numbered from 0, of the first <code>Throwable</code>
  237. * that matches the specified type in the chain of <code>Throwable</code>s
  238. * held in this delegate's <code>Nestable</code> with an index greater than
  239. * or equal to the specified index, or -1 if the type is not found.
  240. *
  241. * @param type <code>Class</code> to be found
  242. * @param fromIndex the index, numbered from 0, of the starting position in
  243. * the chain to be searched
  244. * @return index of the first occurrence of the type in the chain, or -1 if
  245. * the type is not found
  246. * @throws IndexOutOfBoundsException if the <code>fromIndex</code> argument
  247. * is negative or not less than the count of <code>Throwable</code>s in the
  248. * chain
  249. * @since 2.0
  250. */
  251. public int indexOfThrowable(Class type, int fromIndex) {
  252. if (fromIndex < 0) {
  253. throw new IndexOutOfBoundsException("The start index was out of bounds: " + fromIndex);
  254. }
  255. Throwable[] throwables = ExceptionUtils.getThrowables(this.nestable);
  256. if (fromIndex >= throwables.length) {
  257. throw new IndexOutOfBoundsException("The start index was out of bounds: "
  258. + fromIndex + " >= " + throwables.length);
  259. }
  260. for (int i = fromIndex; i < throwables.length; i++) {
  261. if (throwables[i].getClass().equals(type)) {
  262. return i;
  263. }
  264. }
  265. return -1;
  266. }
  267. /**
  268. * Prints the stack trace of this exception the the standar error
  269. * stream.
  270. */
  271. public void printStackTrace() {
  272. printStackTrace(System.err);
  273. }
  274. /**
  275. * Prints the stack trace of this exception to the specified
  276. * stream.
  277. *
  278. * @param out <code>PrintStream</code> to use for output.
  279. * @see #printStackTrace(PrintWriter)
  280. */
  281. public void printStackTrace(PrintStream out) {
  282. synchronized (out) {
  283. PrintWriter pw = new PrintWriter(out, false);
  284. printStackTrace(pw);
  285. // Flush the PrintWriter before it's GC'ed.
  286. pw.flush();
  287. }
  288. }
  289. /**
  290. * Prints the stack trace of this exception to the specified
  291. * writer. If the Throwable class has a <code>getCause</code>
  292. * method (i.e. running on jre1.4 or higher), this method just
  293. * uses Throwable's printStackTrace() method. Otherwise, generates
  294. * the stack-trace, by taking into account the 'topDown' and
  295. * 'trimStackFrames' parameters. The topDown and trimStackFrames
  296. * are set to 'true' by default (produces jre1.4-like stack trace).
  297. *
  298. * @param out <code>PrintWriter</code> to use for output.
  299. */
  300. public void printStackTrace(PrintWriter out) {
  301. Throwable throwable = this.nestable;
  302. // if running on jre1.4 or higher, use default printStackTrace
  303. if (ExceptionUtils.isThrowableNested()) {
  304. if (throwable instanceof Nestable) {
  305. ((Nestable)throwable).printPartialStackTrace(out);
  306. } else {
  307. throwable.printStackTrace(out);
  308. }
  309. return;
  310. }
  311. // generating the nested stack trace
  312. List stacks = new ArrayList();
  313. while (throwable != null) {
  314. String[] st = getStackFrames(throwable);
  315. stacks.add(st);
  316. throwable = ExceptionUtils.getCause(throwable);
  317. }
  318. // If NOT topDown, reverse the stack
  319. String separatorLine = "Caused by: ";
  320. if (!topDown) {
  321. separatorLine = "Rethrown as: ";
  322. Collections.reverse(stacks);
  323. }
  324. // Remove the repeated lines in the stack
  325. if (trimStackFrames) trimStackFrames(stacks);
  326. synchronized (out) {
  327. for (Iterator iter=stacks.iterator(); iter.hasNext();) {
  328. String[] st = (String[]) iter.next();
  329. for (int i=0, len=st.length; i < len; i++) {
  330. out.println(st[i]);
  331. }
  332. if (iter.hasNext())
  333. out.print(separatorLine);
  334. }
  335. }
  336. }
  337. /**
  338. * Captures the stack trace associated with the specified
  339. * <code>Throwable</code> object, decomposing it into a list of
  340. * stack frames.
  341. *
  342. * @param t The <code>Throwable</code>.
  343. * @return An array of strings describing each stack frame.
  344. * @since 2.0
  345. */
  346. protected String[] getStackFrames(Throwable t) {
  347. StringWriter sw = new StringWriter();
  348. PrintWriter pw = new PrintWriter(sw, true);
  349. // Avoid infinite loop between decompose() and printStackTrace().
  350. if (t instanceof Nestable) {
  351. ((Nestable) t).printPartialStackTrace(pw);
  352. } else {
  353. t.printStackTrace(pw);
  354. }
  355. return ExceptionUtils.getStackFrames(sw.getBuffer().toString());
  356. }
  357. /**
  358. * Trims the stack frames. The first set is left untouched. The rest
  359. * of the frames are truncated from the bottom by comparing with
  360. * one just on top.
  361. *
  362. * @param stacks The list containing String[] elements
  363. * @since 2.0
  364. */
  365. protected void trimStackFrames(List stacks) {
  366. for (int size=stacks.size(), i=size-1; i > 0; i--) {
  367. String[] curr = (String[]) stacks.get(i);
  368. String[] next = (String[]) stacks.get(i-1);
  369. List currList = new ArrayList(Arrays.asList(curr));
  370. List nextList = new ArrayList(Arrays.asList(next));
  371. ExceptionUtils.removeCommonFrames(currList, nextList);
  372. int trimmed = curr.length - currList.size();
  373. if (trimmed > 0) {
  374. currList.add("\t... "+trimmed+" more");
  375. stacks.set(
  376. i,
  377. currList.toArray(new String[currList.size()])
  378. );
  379. }
  380. }
  381. }
  382. }