root/trunk/src/java/org/jcoderz/commons/util/ThrowableUtil.java

Revision 1625, 13.3 kB (checked in by amandel, 23 months ago)

Add utility method to get property like values from any Throwable.
Fix check for VOID return type.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons.util;
34
35import java.io.PrintWriter;
36import java.io.StringWriter;
37import java.lang.reflect.InvocationTargetException;
38import java.lang.reflect.Method;
39import java.lang.reflect.Modifier;
40import java.util.HashMap;
41import java.util.Map;
42import java.util.logging.Level;
43import java.util.logging.Logger;
44
45import org.jcoderz.commons.Loggable;
46
47
48/**
49 * Helper class around throwables.
50 * @author Andreas Mandel
51 */
52public final class ThrowableUtil
53{
54   private static final int MAX_REASONABLE_PARAMETER_LENGTH = 10000;
55   /** Name of getter methods start with this prefix. */
56   private static final String GETTER_METHOD_PREFIX = "get";
57   /** Name of getter methods start with this prefix. */
58   private static final String BOOLEAN_GETTER_METHOD_PREFIX = "is";
59   /** Length of the getter prefix. */
60   private static final int GETTER_METHOD_PREFIX_LENGTH
61       = GETTER_METHOD_PREFIX.length();
62   /** Length of the boolean getter prefix. */
63   private static final int BOOLEAN_GETTER_METHOD_PREFIX_LENGTH
64       = BOOLEAN_GETTER_METHOD_PREFIX.length();
65   
66   /**
67    * Stores the Throwable.getCause() method if this method is available.
68    * This should be the case for all JDKs > 1.4.
69    */
70   private static final Method GET_CAUSE;
71   /**
72    * Stores the Throwable.initCause(Throwable) method if this method
73    * is available.
74    * This should be the case for all JDKs > 1.4.
75    */
76   private static final Method INIT_CAUSE;
77   /** A empty object array. */
78   private static final Object[] EMPTY_ARRAY = new Object[0];
79
80   private static final String CLASSNAME = ThrowableUtil.class.getName();
81   private static final Logger logger = Logger.getLogger(CLASSNAME);
82
83   private static final int MAX_NESTING_DEPTH = 15;
84
85   static
86   {
87      Method theGetCauseMethod = null;
88      Method theInitCauseMethod = null;
89      try
90      {
91         theGetCauseMethod
92               = Throwable.class.getDeclaredMethod("getCause", new Class[0]);
93         theInitCauseMethod
94               = Throwable.class.getDeclaredMethod("initCause",
95                  new Class[] {Throwable.class});
96      }
97      catch (Exception ex)
98      {
99         // Warning, cause this should not fail with JDK > 1.4
100         logger.log(Level.WARNING, "Could not initialize, will run without.",
101               ex);
102      }
103      GET_CAUSE = theGetCauseMethod;
104      INIT_CAUSE = theInitCauseMethod;
105   }
106
107   private ThrowableUtil ()
108   {
109      // NO Instances
110   }
111
112   /**
113    * Tries to fix the exception chaining for the given Throwable.
114    * Some exception classes still use a none standard way
115    * to nest exceptions. This method tries best to detect this
116    * classes and pass the nested exceptions into the standard
117    * nesting mechanism available since JDK1.4 with the throwable
118    * class. It is save to call this method several times for a
119    * give exception.
120    * @param ex the exception to be checked.
121    */
122   public static void fixChaining (Throwable ex)
123   {
124      try
125      {
126         Throwable current = ex;
127         int nesting = 0;
128         while (INIT_CAUSE != null && current != null
129               && !(current instanceof Loggable))
130         {
131            if (nesting++ > MAX_NESTING_DEPTH)
132            {
133               logger.log(Level.FINE,
134                     "Stopped fixing exception nesting cause max depth "
135                     + "reached for given exception.", ex);
136               break;
137            }
138            if (current.getCause() == null)
139            {
140               final Method theGetCauseMethod
141                     = findGetCauseMethod(current.getClass().getMethods());
142               if (theGetCauseMethod != null)
143               {
144                  initCause(current, theGetCauseMethod);
145               }
146            }
147            current = current.getCause();
148         }
149      }
150      catch (Exception unexpected)
151      {
152         // do not risk any side effect here
153         logger.log(
154               Level.SEVERE, "Unexpected exception, ignored.", unexpected);
155      }
156   }
157
158   /**
159    * Pull up nested information.
160    * This method goes down the exception chain of the given
161    * loggable and if it find getters for properties,
162    * like <code>getSql()</code> adds the values of these
163    * properties as parameters to the loggable. The search is
164    * stopped when either a {@link Loggable} is found in the list,
165    * the end of the chain is reached or after MAX_NESTING_DEPTH
166    * steps down the chain.
167    * @param loggable the Loggable to be feed with parameters.
168    */
169   public static void collectNestedData (Loggable loggable)
170   {
171      try
172      {
173         Throwable current = loggable.getCause();
174         int nesting = 0;
175         while (current != null)
176         {
177            if (nesting++ > MAX_NESTING_DEPTH)
178            {
179               logger.log(Level.FINE,
180                     "Stopped collecting nested information max depth "
181                     + "reached for given exception.", loggable);
182               break;
183            }
184            if (!(current instanceof Loggable))
185            {
186                collectParameters(loggable, current, nesting);
187            }
188            current = current.getCause();
189         }
190      }
191      catch (Exception unexpected)
192      {
193         // do not risk any side effect here
194         logger.log(
195               Level.SEVERE, "Unexpected exception, ignored.", unexpected);
196      }
197   }
198
199   /**
200    * Tries to read additional property like information from this throwable
201    * and fills it in a map suitable for detailed information output.
202    * @param thr the throwable to analyze.
203    * @return a Map pointing from String property names to the value.
204    */
205   public static Map/*<String, Object>*/ getProperties(Throwable thr)
206   {
207       final Map/*<String, Object>*/ result = new HashMap();
208       final Method[] methods = thr.getClass().getMethods();
209       for (int i = 0; i < methods.length; i++)
210       {
211          final int modifier = methods[i].getModifiers();
212          if (methods[i].getDeclaringClass() != Throwable.class
213              && methods[i].getDeclaringClass() != Object.class
214              && methods[i].getParameterTypes().length == 0
215              && Modifier.isPublic(modifier)
216              && !Modifier.isStatic(modifier)
217              && !methods[i].getReturnType().equals(Void.TYPE))
218          {
219              try
220              {
221                  if (methods[i].getName().startsWith(GETTER_METHOD_PREFIX))
222                  {
223                      final Object value
224                          = methods[i].invoke(thr, (Object[]) null);
225                      final String key
226                          = methods[i].getName().substring(
227                              GETTER_METHOD_PREFIX_LENGTH);
228                      result.put(key, value);
229                  }
230                  else if (methods[i].getName().startsWith(
231                          BOOLEAN_GETTER_METHOD_PREFIX)
232                      && (methods[i].getReturnType().equals(Boolean.class)
233                          || methods[i].getReturnType().equals(
234                              java.lang.Boolean.TYPE)))
235                  {
236                      final Object value
237                          = methods[i].invoke(thr, (Object[]) null);
238                      final String key
239                          = methods[i].getName().substring(
240                              BOOLEAN_GETTER_METHOD_PREFIX_LENGTH);
241                      result.put(key, value);
242                  }
243              }
244              catch (InvocationTargetException e)
245              {
246                  // Ignore this property, continue with next
247              }
248              catch (IllegalArgumentException e)
249              {
250                  // Ignore this property, continue with next
251              }
252              catch (IllegalAccessException e)
253              {
254                  // Ignore this property, continue with next
255              }
256          }
257       }
258       return result;
259   }
260   
261   /**
262    * Dumps the stack trace of the given throwable to its String representation.
263    * @param thr the throwable to dump the stack trace from.
264    * @return a String representation of the given throwable
265    * @see Throwable#printStackTrace()
266    */
267   public static String toString (Throwable thr)
268   {
269       final StringWriter sw = new StringWriter();
270       PrintWriter pw = null;
271       try
272       {
273           pw = new PrintWriter(sw);
274           thr.printStackTrace(pw);
275       }
276       finally
277       {
278           IoUtil.close(pw);
279           IoUtil.close(sw);
280       }
281       return sw.toString();
282   }
283
284   static Method findGetCauseMethod (final Method[] methods)
285   {
286      Method theGetCauseMethod = null;
287      for (int i = 0; i < methods.length; i++)
288      {
289         if (methods[i].getDeclaringClass() == Throwable.class)
290         {
291            continue;
292         }
293         final int modifier = methods[i].getModifiers();
294         if (methods[i].getParameterTypes().length == 0
295             && Modifier.isPublic(modifier)
296             && !Modifier.isStatic(modifier)
297             && Throwable.class.isAssignableFrom(methods[i].getReturnType()))
298         {
299             // if the method is called getCause, assume it does
300             // what it is named FIXES #76
301             if (methods[i].getName().equals("getCause"))
302             {
303                 theGetCauseMethod = methods[i];
304                 break;
305             }
306            if (theGetCauseMethod != null)
307            {
308               // 2nd hit, safety first
309               logger.fine("Found 2 matching methods "  + theGetCauseMethod
310                     + " or " + methods[i] + ".");
311               theGetCauseMethod = null;
312               break;
313            }
314            theGetCauseMethod = methods[i];
315         }
316      }
317      return theGetCauseMethod;
318   }
319
320
321   private static void initCause (Throwable current, Method theGetCauseMethod)
322   {
323      try
324      {
325         final Throwable cause
326               = (Throwable) theGetCauseMethod.invoke(
327                  current, EMPTY_ARRAY);
328         INIT_CAUSE.invoke(current, new Object[] {cause});
329      }
330      catch (Exception e)
331      {
332         logger.log(Level.FINEST, "Failed to init cause for " + current
333               + " using " + theGetCauseMethod + " got a exception.", e);
334      }
335   }
336
337   private static void collectParameters (
338       Loggable loggable, Throwable thr, int nesting)
339       throws IllegalAccessException, InvocationTargetException
340   {
341       final Method[] methods = thr.getClass().getMethods();
342       for (int i = 0; i < methods.length; i++)
343       {
344          final int modifier = methods[i].getModifiers();
345          if (methods[i].getDeclaringClass() != Throwable.class
346              && methods[i].getDeclaringClass() != Object.class
347              && methods[i].getParameterTypes().length == 0
348              && methods[i].getExceptionTypes().length == 0
349              && Modifier.isPublic(modifier)
350              && !Modifier.isStatic(modifier)
351              && methods[i].getName().startsWith(GETTER_METHOD_PREFIX)
352              && !methods[i].getReturnType().equals(Void.TYPE))
353          {
354              final Object result = methods[i].invoke(thr, (Object[]) null);
355              if (result != null && result != thr.getCause())
356              {
357                  loggable.addParameter(
358                      "CAUSE_" + nesting + "_" + thr.getClass().getName()
359                          + "#" + methods[i].getName().substring(
360                              GETTER_METHOD_PREFIX_LENGTH),
361                      asString(result));
362              }
363          }
364       }
365   }
366
367    private static String asString (Object obj)
368    {
369        String result;
370        if (obj instanceof Object[])
371        {
372            result = ArraysUtil.toString((Object[]) obj);
373        }
374        else
375        {
376            result = String.valueOf(obj);
377        }
378        return StringUtil.trimLength(
379            result,
380            MAX_REASONABLE_PARAMETER_LENGTH);
381    }
382}
Note: See TracBrowser for help on using the browser.