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 */ 017package org.apache.isis.core.commons.exceptions; 018 019 020import java.io.PrintWriter; 021import java.io.StringWriter; 022import java.lang.reflect.Field; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.sql.SQLException; 026import java.util.ArrayList; 027import java.util.List; 028 029/** 030 * <p>Provides utilities for manipulating and examining 031 * <code>Throwable</code> objects.</p> 032 * 033 * @author Daniel L. Rall 034 * @author Dmitri Plotnikov 035 * @author Stephen Colebourne 036 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a> 037 * @author Pete Gieser 038 * @since 1.0 039 * @version $Id: ExceptionUtils.java 594278 2007-11-12 19:58:30Z bayard $ 040 */ 041// portions copied in from commons-lang 2.6 042public class ExceptionUtils { 043 044 /** 045 * <p>The names of methods commonly used to access a wrapped exception.</p> 046 */ 047 private static String[] CAUSE_METHOD_NAMES = { 048 "getCause", 049 "getNextException", 050 "getTargetException", 051 "getException", 052 "getSourceException", 053 "getRootCause", 054 "getCausedByException", 055 "getNested", 056 "getLinkedException", 057 "getNestedException", 058 "getLinkedCause", 059 "getThrowable", 060 }; 061 062 /** 063 * <p>The Method object for Java 1.4 getCause.</p> 064 */ 065 private static final Method THROWABLE_CAUSE_METHOD; 066 067 /** 068 * <p>The Method object for Java 1.4 initCause.</p> 069 */ 070 private static final Method THROWABLE_INITCAUSE_METHOD; 071 072 static { 073 Method causeMethod; 074 try { 075 causeMethod = Throwable.class.getMethod("getCause", null); 076 } catch (Exception e) { 077 causeMethod = null; 078 } 079 THROWABLE_CAUSE_METHOD = causeMethod; 080 try { 081 causeMethod = Throwable.class.getMethod("initCause", new Class[]{Throwable.class}); 082 } catch (Exception e) { 083 causeMethod = null; 084 } 085 THROWABLE_INITCAUSE_METHOD = causeMethod; 086 } 087 088 /** 089 * <p> 090 * Public constructor allows an instance of <code>ExceptionUtils</code> to be created, although that is not 091 * normally necessary. 092 * </p> 093 */ 094 public ExceptionUtils() { 095 super(); 096 } 097 098 //----------------------------------------------------------------------- 099 /** 100 * <p>Introspects the <code>Throwable</code> to obtain the cause.</p> 101 * 102 * <p>The method searches for methods with specific names that return a 103 * <code>Throwable</code> object. This will pick up most wrapping exceptions, 104 * including those from JDK 1.4, and 105 * {@link org.apache.commons.lang.exception.NestableException NestableException}. 106 * The method names can be added to using {@link #addCauseMethodName(String)}.</p> 107 * 108 * <p>The default list searched for are:</p> 109 * <ul> 110 * <li><code>getCause()</code></li> 111 * <li><code>getNextException()</code></li> 112 * <li><code>getTargetException()</code></li> 113 * <li><code>getException()</code></li> 114 * <li><code>getSourceException()</code></li> 115 * <li><code>getRootCause()</code></li> 116 * <li><code>getCausedByException()</code></li> 117 * <li><code>getNested()</code></li> 118 * </ul> 119 * 120 * <p>In the absence of any such method, the object is inspected for a 121 * <code>detail</code> field assignable to a <code>Throwable</code>.</p> 122 * 123 * <p>If none of the above is found, returns <code>null</code>.</p> 124 * 125 * @param throwable the throwable to introspect for a cause, may be null 126 * @return the cause of the <code>Throwable</code>, 127 * <code>null</code> if none found or null throwable input 128 * @since 1.0 129 */ 130 public static Throwable getCause(Throwable throwable) { 131 synchronized(CAUSE_METHOD_NAMES) { 132 return getCause(throwable, CAUSE_METHOD_NAMES); 133 } 134 } 135 136 /** 137 * <p>Introspects the <code>Throwable</code> to obtain the cause.</p> 138 * 139 * <ol> 140 * <li>Try known exception types.</li> 141 * <li>Try the supplied array of method names.</li> 142 * <li>Try the field 'detail'.</li> 143 * </ol> 144 * 145 * <p>A <code>null</code> set of method names means use the default set. 146 * A <code>null</code> in the set of method names will be ignored.</p> 147 * 148 * @param throwable the throwable to introspect for a cause, may be null 149 * @param methodNames the method names, null treated as default set 150 * @return the cause of the <code>Throwable</code>, 151 * <code>null</code> if none found or null throwable input 152 * @since 1.0 153 */ 154 public static Throwable getCause(Throwable throwable, String[] methodNames) { 155 if (throwable == null) { 156 return null; 157 } 158 Throwable cause = getCauseUsingWellKnownTypes(throwable); 159 if (cause == null) { 160 if (methodNames == null) { 161 synchronized(CAUSE_METHOD_NAMES) { 162 methodNames = CAUSE_METHOD_NAMES; 163 } 164 } 165 for (int i = 0; i < methodNames.length; i++) { 166 String methodName = methodNames[i]; 167 if (methodName != null) { 168 cause = getCauseUsingMethodName(throwable, methodName); 169 if (cause != null) { 170 break; 171 } 172 } 173 } 174 175 if (cause == null) { 176 cause = getCauseUsingFieldName(throwable, "detail"); 177 } 178 } 179 return cause; 180 } 181 182 /** 183 * <p>Finds a <code>Throwable</code> for known types.</p> 184 * 185 * <p>Uses <code>instanceof</code> checks to examine the exception, 186 * looking for well known types which could contain chained or 187 * wrapped exceptions.</p> 188 * 189 * @param throwable the exception to examine 190 * @return the wrapped exception, or <code>null</code> if not found 191 */ 192 private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) { 193 /*if (throwable instanceof Nestable) { 194 return ((Nestable) throwable).getCause(); 195 } else */ 196 if (throwable instanceof SQLException) { 197 return ((SQLException) throwable).getNextException(); 198 } else if (throwable instanceof InvocationTargetException) { 199 return ((InvocationTargetException) throwable).getTargetException(); 200 } else { 201 return null; 202 } 203 } 204 205 /** 206 * <p>Finds a <code>Throwable</code> by method name.</p> 207 * 208 * @param throwable the exception to examine 209 * @param methodName the name of the method to find and invoke 210 * @return the wrapped exception, or <code>null</code> if not found 211 */ 212 private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) { 213 Method method = null; 214 try { 215 method = throwable.getClass().getMethod(methodName, null); 216 } catch (NoSuchMethodException ignored) { 217 // exception ignored 218 } catch (SecurityException ignored) { 219 // exception ignored 220 } 221 222 if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { 223 try { 224 return (Throwable) method.invoke(throwable, /*ArrayUtils.*/EMPTY_OBJECT_ARRAY); 225 } catch (IllegalAccessException ignored) { 226 // exception ignored 227 } catch (IllegalArgumentException ignored) { 228 // exception ignored 229 } catch (InvocationTargetException ignored) { 230 // exception ignored 231 } 232 } 233 return null; 234 } 235 236 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 237 238 /** 239 * <p>Finds a <code>Throwable</code> by field name.</p> 240 * 241 * @param throwable the exception to examine 242 * @param fieldName the name of the attribute to examine 243 * @return the wrapped exception, or <code>null</code> if not found 244 */ 245 private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) { 246 Field field = null; 247 try { 248 field = throwable.getClass().getField(fieldName); 249 } catch (NoSuchFieldException ignored) { 250 // exception ignored 251 } catch (SecurityException ignored) { 252 // exception ignored 253 } 254 255 if (field != null && Throwable.class.isAssignableFrom(field.getType())) { 256 try { 257 return (Throwable) field.get(throwable); 258 } catch (IllegalAccessException ignored) { 259 // exception ignored 260 } catch (IllegalArgumentException ignored) { 261 // exception ignored 262 } 263 } 264 return null; 265 } 266 267 //----------------------------------------------------------------------- 268 /** 269 * <p>Checks if the Throwable class has a <code>getCause</code> method.</p> 270 * 271 * <p>This is true for JDK 1.4 and above.</p> 272 * 273 * @return true if Throwable is nestable 274 * @since 2.0 275 */ 276 public static boolean isThrowableNested() { 277 return THROWABLE_CAUSE_METHOD != null; 278 } 279 280 /** 281 * <p>Checks whether this <code>Throwable</code> class can store a cause.</p> 282 * 283 * <p>This method does <b>not</b> check whether it actually does store a cause.<p> 284 * 285 * @param throwable the <code>Throwable</code> to examine, may be null 286 * @return boolean <code>true</code> if nested otherwise <code>false</code> 287 * @since 2.0 288 */ 289 public static boolean isNestedThrowable(Throwable throwable) { 290 if (throwable == null) { 291 return false; 292 } 293 294 /*if (throwable instanceof Nestable) { 295 return true; 296 } else*/ if (throwable instanceof SQLException) { 297 return true; 298 } else if (throwable instanceof InvocationTargetException) { 299 return true; 300 } else if (isThrowableNested()) { 301 return true; 302 } 303 304 Class cls = throwable.getClass(); 305 synchronized(CAUSE_METHOD_NAMES) { 306 for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) { 307 try { 308 Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], null); 309 if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) { 310 return true; 311 } 312 } catch (NoSuchMethodException ignored) { 313 // exception ignored 314 } catch (SecurityException ignored) { 315 // exception ignored 316 } 317 } 318 } 319 320 try { 321 Field field = cls.getField("detail"); 322 if (field != null) { 323 return true; 324 } 325 } catch (NoSuchFieldException ignored) { 326 // exception ignored 327 } catch (SecurityException ignored) { 328 // exception ignored 329 } 330 331 return false; 332 } 333 334 /** 335 * <p>Returns the list of <code>Throwable</code> objects in the 336 * exception chain.</p> 337 * 338 * <p>A throwable without cause will return an array containing 339 * one element - the input throwable. 340 * A throwable with one cause will return an array containing 341 * two elements. - the input throwable and the cause throwable. 342 * A <code>null</code> throwable will return an array of size zero.</p> 343 * 344 * <p>From version 2.2, this method handles recursive cause structures 345 * that might otherwise cause infinite loops. The cause chain is 346 * processed until the end is reached, or until the next item in the 347 * chain is already in the result set.</p> 348 * 349 * @see #getThrowableList(Throwable) 350 * @param throwable the throwable to inspect, may be null 351 * @return the array of throwables, never null 352 */ 353 public static Throwable[] getThrowables(Throwable throwable) { 354 List list = getThrowableList(throwable); 355 return (Throwable[]) list.toArray(new Throwable[list.size()]); 356 } 357 358 /** 359 * <p>Returns the list of <code>Throwable</code> objects in the 360 * exception chain.</p> 361 * 362 * <p>A throwable without cause will return a list containing 363 * one element - the input throwable. 364 * A throwable with one cause will return a list containing 365 * two elements. - the input throwable and the cause throwable. 366 * A <code>null</code> throwable will return a list of size zero.</p> 367 * 368 * <p>This method handles recursive cause structures that might 369 * otherwise cause infinite loops. The cause chain is processed until 370 * the end is reached, or until the next item in the chain is already 371 * in the result set.</p> 372 * 373 * @param throwable the throwable to inspect, may be null 374 * @return the list of throwables, never null 375 * @since Commons Lang 2.2 376 */ 377 public static List getThrowableList(Throwable throwable) { 378 List list = new ArrayList(); 379 while (throwable != null && list.contains(throwable) == false) { 380 list.add(throwable); 381 throwable = ExceptionUtils.getCause(throwable); 382 } 383 return list; 384 } 385 386 //----------------------------------------------------------------------- 387 /** 388 * <p>A way to get the entire nested stack-trace of an throwable.</p> 389 * 390 * <p>The result of this method is highly dependent on the JDK version 391 * and whether the exceptions override printStackTrace or not.</p> 392 * 393 * @param throwable the <code>Throwable</code> to be examined 394 * @return the nested stack trace, with the root cause first 395 * @since 2.0 396 */ 397 public static String getFullStackTrace(Throwable throwable) { 398 StringWriter sw = new StringWriter(); 399 PrintWriter pw = new PrintWriter(sw, true); 400 Throwable[] ts = getThrowables(throwable); 401 for (int i = 0; i < ts.length; i++) { 402 ts[i].printStackTrace(pw); 403 if (isNestedThrowable(ts[i])) { 404 break; 405 } 406 } 407 return sw.getBuffer().toString(); 408 } 409 410}