001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.lang.exception; 018 019 import java.io.PrintStream; 020 import java.io.PrintWriter; 021 import java.io.Serializable; 022 import java.io.StringWriter; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.Collections; 026 import java.util.Iterator; 027 import java.util.List; 028 029 /** 030 * <p>A shared implementation of the nestable exception functionality.</p> 031 * <p> 032 * The code is shared between 033 * {@link org.apache.commons.lang.exception.NestableError NestableError}, 034 * {@link org.apache.commons.lang.exception.NestableException NestableException} and 035 * {@link org.apache.commons.lang.exception.NestableRuntimeException NestableRuntimeException}. 036 * </p> 037 * 038 * @author Apache Software Foundation 039 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a> 040 * @author Daniel L. Rall 041 * @author <a href="mailto:knielsen@apache.org">Kasper Nielsen</a> 042 * @author <a href="mailto:steven@caswell.name">Steven Caswell</a> 043 * @author Sean C. Sullivan 044 * @since 1.0 045 * @version $Id: NestableDelegate.java 905636 2010-02-02 14:03:32Z niallp $ 046 */ 047 public class NestableDelegate implements Serializable { 048 049 /** 050 * Required for serialization support. 051 * 052 * @see java.io.Serializable 053 */ 054 private static final long serialVersionUID = 1L; 055 056 /** 057 * Constructor error message. 058 */ 059 private transient static final String MUST_BE_THROWABLE = 060 "The Nestable implementation passed to the NestableDelegate(Nestable) " 061 + "constructor must extend java.lang.Throwable"; 062 063 /** 064 * Holds the reference to the exception or error that we're 065 * wrapping (which must be a {@link 066 * org.apache.commons.lang.exception.Nestable} implementation). 067 */ 068 private Throwable nestable = null; 069 070 /** 071 * Whether to print the stack trace top-down. 072 * This public flag may be set by calling code, typically in initialisation. 073 * This exists for backwards compatability, setting it to false will return 074 * the library to v1.0 behaviour (but will affect all users of the library 075 * in the classloader). 076 * @since 2.0 077 */ 078 public static boolean topDown = true; 079 080 /** 081 * Whether to trim the repeated stack trace. 082 * This public flag may be set by calling code, typically in initialisation. 083 * This exists for backwards compatability, setting it to false will return 084 * the library to v1.0 behaviour (but will affect all users of the library 085 * in the classloader). 086 * @since 2.0 087 */ 088 public static boolean trimStackFrames = true; 089 090 /** 091 * Whether to match subclasses via indexOf. 092 * This public flag may be set by calling code, typically in initialisation. 093 * This exists for backwards compatability, setting it to false will return 094 * the library to v2.0 behaviour (but will affect all users of the library 095 * in the classloader). 096 * @since 2.1 097 */ 098 public static boolean matchSubclasses = true; 099 100 /** 101 * Constructs a new <code>NestableDelegate</code> instance to manage the 102 * specified <code>Nestable</code>. 103 * 104 * @param nestable the Nestable implementation (<i>must</i> extend 105 * {@link java.lang.Throwable}) 106 * @since 2.0 107 */ 108 public NestableDelegate(Nestable nestable) { 109 if (nestable instanceof Throwable) { 110 this.nestable = (Throwable) nestable; 111 } else { 112 throw new IllegalArgumentException(MUST_BE_THROWABLE); 113 } 114 } 115 116 /** 117 * Returns the error message of the <code>Throwable</code> in the chain of <code>Throwable</code>s at the 118 * specified index, numbered from 0. 119 * 120 * @param index 121 * the index of the <code>Throwable</code> in the chain of <code>Throwable</code>s 122 * @return the error message, or null if the <code>Throwable</code> at the specified index in the chain does not 123 * contain a message 124 * @throws IndexOutOfBoundsException 125 * if the <code>index</code> argument is negative or not less than the count of <code>Throwable</code>s 126 * in the chain 127 * @since 2.0 128 */ 129 public String getMessage(int index) { 130 Throwable t = this.getThrowable(index); 131 if (Nestable.class.isInstance(t)) { 132 return ((Nestable) t).getMessage(0); 133 } 134 return t.getMessage(); 135 } 136 137 /** 138 * Returns the full message contained by the <code>Nestable</code> and any nested <code>Throwable</code>s. 139 * 140 * @param baseMsg 141 * the base message to use when creating the full message. Should be generally be called via 142 * <code>nestableHelper.getMessage(super.getMessage())</code>, where <code>super</code> is an 143 * instance of {@link java.lang.Throwable}. 144 * @return The concatenated message for this and all nested <code>Throwable</code>s 145 * @since 2.0 146 */ 147 public String getMessage(String baseMsg) { 148 Throwable nestedCause = ExceptionUtils.getCause(this.nestable); 149 String causeMsg = nestedCause == null ? null : nestedCause.getMessage(); 150 if (nestedCause == null || causeMsg == null) { 151 return baseMsg; // may be null, which is a valid result 152 } 153 if (baseMsg == null) { 154 return causeMsg; 155 } 156 return baseMsg + ": " + causeMsg; 157 } 158 159 /** 160 * Returns the error message of this and any nested <code>Throwable</code>s in an array of Strings, one element 161 * for each message. Any <code>Throwable</code> not containing a message is represented in the array by a null. 162 * This has the effect of cause the length of the returned array to be equal to the result of the 163 * {@link #getThrowableCount()} operation. 164 * 165 * @return the error messages 166 * @since 2.0 167 */ 168 public String[] getMessages() { 169 Throwable[] throwables = this.getThrowables(); 170 String[] msgs = new String[throwables.length]; 171 for (int i = 0; i < throwables.length; i++) { 172 msgs[i] = 173 (Nestable.class.isInstance(throwables[i]) 174 ? ((Nestable) throwables[i]).getMessage(0) 175 : throwables[i].getMessage()); 176 } 177 return msgs; 178 } 179 180 /** 181 * Returns the <code>Throwable</code> in the chain of 182 * <code>Throwable</code>s at the specified index, numbered from 0. 183 * 184 * @param index the index, numbered from 0, of the <code>Throwable</code> in 185 * the chain of <code>Throwable</code>s 186 * @return the <code>Throwable</code> 187 * @throws IndexOutOfBoundsException if the <code>index</code> argument is 188 * negative or not less than the count of <code>Throwable</code>s in the 189 * chain 190 * @since 2.0 191 */ 192 public Throwable getThrowable(int index) { 193 if (index == 0) { 194 return this.nestable; 195 } 196 Throwable[] throwables = this.getThrowables(); 197 return throwables[index]; 198 } 199 200 /** 201 * Returns the number of <code>Throwable</code>s contained in the 202 * <code>Nestable</code> contained by this delegate. 203 * 204 * @return the throwable count 205 * @since 2.0 206 */ 207 public int getThrowableCount() { 208 return ExceptionUtils.getThrowableCount(this.nestable); 209 } 210 211 /** 212 * Returns this delegate's <code>Nestable</code> and any nested 213 * <code>Throwable</code>s in an array of <code>Throwable</code>s, one 214 * element for each <code>Throwable</code>. 215 * 216 * @return the <code>Throwable</code>s 217 * @since 2.0 218 */ 219 public Throwable[] getThrowables() { 220 return ExceptionUtils.getThrowables(this.nestable); 221 } 222 223 /** 224 * Returns the index, numbered from 0, of the first <code>Throwable</code> 225 * that matches the specified type, or a subclass, in the chain of <code>Throwable</code>s 226 * with an index greater than or equal to the specified index. 227 * The method returns -1 if the specified type is not found in the chain. 228 * <p> 229 * NOTE: From v2.1, we have clarified the <code>Nestable</code> interface 230 * such that this method matches subclasses. 231 * If you want to NOT match subclasses, please use 232 * {@link ExceptionUtils#indexOfThrowable(Throwable, Class, int)} 233 * (which is avaiable in all versions of lang). 234 * An alternative is to use the public static flag {@link #matchSubclasses} 235 * on <code>NestableDelegate</code>, however this is not recommended. 236 * 237 * @param type the type to find, subclasses match, null returns -1 238 * @param fromIndex the index, numbered from 0, of the starting position in 239 * the chain to be searched 240 * @return index of the first occurrence of the type in the chain, or -1 if 241 * the type is not found 242 * @throws IndexOutOfBoundsException if the <code>fromIndex</code> argument 243 * is negative or not less than the count of <code>Throwable</code>s in the 244 * chain 245 * @since 2.0 246 */ 247 public int indexOfThrowable(Class type, int fromIndex) { 248 if (type == null) { 249 return -1; 250 } 251 if (fromIndex < 0) { 252 throw new IndexOutOfBoundsException("The start index was out of bounds: " + fromIndex); 253 } 254 Throwable[] throwables = ExceptionUtils.getThrowables(this.nestable); 255 if (fromIndex >= throwables.length) { 256 throw new IndexOutOfBoundsException("The start index was out of bounds: " 257 + fromIndex + " >= " + throwables.length); 258 } 259 if (matchSubclasses) { 260 for (int i = fromIndex; i < throwables.length; i++) { 261 if (type.isAssignableFrom(throwables[i].getClass())) { 262 return i; 263 } 264 } 265 } else { 266 for (int i = fromIndex; i < throwables.length; i++) { 267 if (type.equals(throwables[i].getClass())) { 268 return i; 269 } 270 } 271 } 272 return -1; 273 } 274 275 /** 276 * Prints the stack trace of this exception the the standar error 277 * stream. 278 */ 279 public void printStackTrace() { 280 printStackTrace(System.err); 281 } 282 283 /** 284 * Prints the stack trace of this exception to the specified 285 * stream. 286 * 287 * @param out <code>PrintStream</code> to use for output. 288 * @see #printStackTrace(PrintWriter) 289 */ 290 public void printStackTrace(PrintStream out) { 291 synchronized (out) { 292 PrintWriter pw = new PrintWriter(out, false); 293 printStackTrace(pw); 294 // Flush the PrintWriter before it's GC'ed. 295 pw.flush(); 296 } 297 } 298 299 /** 300 * Prints the stack trace of this exception to the specified 301 * writer. If the Throwable class has a <code>getCause</code> 302 * method (i.e. running on jre1.4 or higher), this method just 303 * uses Throwable's printStackTrace() method. Otherwise, generates 304 * the stack-trace, by taking into account the 'topDown' and 305 * 'trimStackFrames' parameters. The topDown and trimStackFrames 306 * are set to 'true' by default (produces jre1.4-like stack trace). 307 * 308 * @param out <code>PrintWriter</code> to use for output. 309 */ 310 public void printStackTrace(PrintWriter out) { 311 Throwable throwable = this.nestable; 312 // if running on jre1.4 or higher, use default printStackTrace 313 if (ExceptionUtils.isThrowableNested()) { 314 if (throwable instanceof Nestable) { 315 ((Nestable)throwable).printPartialStackTrace(out); 316 } else { 317 throwable.printStackTrace(out); 318 } 319 return; 320 } 321 322 // generating the nested stack trace 323 List stacks = new ArrayList(); 324 while (throwable != null) { 325 String[] st = getStackFrames(throwable); 326 stacks.add(st); 327 throwable = ExceptionUtils.getCause(throwable); 328 } 329 330 // If NOT topDown, reverse the stack 331 String separatorLine = "Caused by: "; 332 if (!topDown) { 333 separatorLine = "Rethrown as: "; 334 Collections.reverse(stacks); 335 } 336 337 // Remove the repeated lines in the stack 338 if (trimStackFrames) { 339 trimStackFrames(stacks); 340 } 341 342 synchronized (out) { 343 for (Iterator iter=stacks.iterator(); iter.hasNext();) { 344 String[] st = (String[]) iter.next(); 345 for (int i=0, len=st.length; i < len; i++) { 346 out.println(st[i]); 347 } 348 if (iter.hasNext()) { 349 out.print(separatorLine); 350 } 351 } 352 } 353 } 354 355 /** 356 * Captures the stack trace associated with the specified 357 * <code>Throwable</code> object, decomposing it into a list of 358 * stack frames. 359 * 360 * @param t The <code>Throwable</code>. 361 * @return An array of strings describing each stack frame. 362 * @since 2.0 363 */ 364 protected String[] getStackFrames(Throwable t) { 365 StringWriter sw = new StringWriter(); 366 PrintWriter pw = new PrintWriter(sw, true); 367 368 // Avoid infinite loop between decompose() and printStackTrace(). 369 if (t instanceof Nestable) { 370 ((Nestable) t).printPartialStackTrace(pw); 371 } else { 372 t.printStackTrace(pw); 373 } 374 return ExceptionUtils.getStackFrames(sw.getBuffer().toString()); 375 } 376 377 /** 378 * Trims the stack frames. The first set is left untouched. The rest 379 * of the frames are truncated from the bottom by comparing with 380 * one just on top. 381 * 382 * @param stacks The list containing String[] elements 383 * @since 2.0 384 */ 385 protected void trimStackFrames(List stacks) { 386 for (int size=stacks.size(), i=size-1; i > 0; i--) { 387 String[] curr = (String[]) stacks.get(i); 388 String[] next = (String[]) stacks.get(i-1); 389 390 List currList = new ArrayList(Arrays.asList(curr)); 391 List nextList = new ArrayList(Arrays.asList(next)); 392 ExceptionUtils.removeCommonFrames(currList, nextList); 393 394 int trimmed = curr.length - currList.size(); 395 if (trimmed > 0) { 396 currList.add("\t... "+trimmed+" more"); 397 stacks.set( 398 i, 399 currList.toArray(new String[currList.size()]) 400 ); 401 } 402 } 403 } 404 }