Project Report: fawkez

Packagesummary org.jcoderz.commons.util

org.jcoderz.commons.util.ThrowableUtil

LineHitsNoteSource
1  /*
2   * $Id: ThrowableUtil.java 1625 2010-04-07 06:33:35Z amandel $
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   */
33  package org.jcoderz.commons.util;
34  
35  import java.io.PrintWriter;
36  import java.io.StringWriter;
37  import java.lang.reflect.InvocationTargetException;
38  import java.lang.reflect.Method;
39  import java.lang.reflect.Modifier;
40  import java.util.HashMap;
41  import java.util.Map;
42  import java.util.logging.Level;
43  import java.util.logging.Logger;
44  
45  import org.jcoderz.commons.Loggable;
46  
47  
48  /**
49   * Helper class around throwables.
50   * @author Andreas Mandel
51   */
52  public 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. */
60100    private static final int GETTER_METHOD_PREFIX_LENGTH
61         = GETTER_METHOD_PREFIX.length();
62     /** Length of the boolean getter prefix. */
63100    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. */
78100    private static final Object[] EMPTY_ARRAY = new Object[0];
79  
8075    private static final String CLASSNAME = ThrowableUtil.class.getName();
81100    private static final Logger logger = Logger.getLogger(CLASSNAME);
82  
83     private static final int MAX_NESTING_DEPTH = 15;
84  
85     static
86     {
87100       Method theGetCauseMethod = null;
88100       Method theInitCauseMethod = null;
89        try
90        {
9175          theGetCauseMethod
92                 = Throwable.class.getDeclaredMethod("getCause", new Class[0]);
9371          theInitCauseMethod
94                 = Throwable.class.getDeclaredMethod("initCause",
95                    new Class[] {Throwable.class});
96        }
970       catch (Exception ex)
98        {
99           // Warning, cause this should not fail with JDK > 1.4
1000          logger.log(Level.WARNING, "Could not initialize, will run without.",
101                 ex);
102100       }
103100       GET_CAUSE = theGetCauseMethod;
104100       INIT_CAUSE = theInitCauseMethod;
105100    }
106  
107     private ThrowableUtil ()
1080    {
109        // NO Instances
1100    }
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        {
126100          Throwable current = ex;
127100          int nesting = 0;
128           while (INIT_CAUSE != null && current != null
129100                && !(current instanceof Loggable))
130           {
131100             if (nesting++ > MAX_NESTING_DEPTH)
132              {
1330                logger.log(Level.FINE,
134                       "Stopped fixing exception nesting cause max depth "
135                       + "reached for given exception.", ex);
1360                break;
137              }
138100             if (current.getCause() == null)
139              {
140100                final Method theGetCauseMethod
141                       = findGetCauseMethod(current.getClass().getMethods());
142100                if (theGetCauseMethod != null)
143                 {
144100                   initCause(current, theGetCauseMethod);
145                 }
146              }
147100             current = current.getCause();
148           }
149        }
1500       catch (Exception unexpected)
151        {
152           // do not risk any side effect here
1530          logger.log(
154                 Level.SEVERE, "Unexpected exception, ignored.", unexpected);
155100       }
156100    }
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        {
173100          Throwable current = loggable.getCause();
174100          int nesting = 0;
175100          while (current != null)
176           {
177100             if (nesting++ > MAX_NESTING_DEPTH)
178              {
1790                logger.log(Level.FINE,
180                       "Stopped collecting nested information max depth "
181                       + "reached for given exception.", loggable);
1820                break;
183              }
184100             if (!(current instanceof Loggable))
185              {
186100                 collectParameters(loggable, current, nesting);
187              }
188100             current = current.getCause();
189           }
190        }
1910       catch (Exception unexpected)
192        {
193           // do not risk any side effect here
1940          logger.log(
195                 Level.SEVERE, "Unexpected exception, ignored.", unexpected);
196100       }
197100    }
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 (1)(2)   public static Map/*<String, Object>*/ getProperties(Throwable thr)
206     {
2070        final Map/*<String, Object>*/ result = new HashMap();
2080        final Method[] methods = thr.getClass().getMethods();
2090        for (int i = 0; i < methods.length; i++)
210         {
2110           final int modifier = methods[i].getModifiers();
2120           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                {
2210                   if (methods[i].getName().startsWith(GETTER_METHOD_PREFIX))
222                    {
2230                       final Object value
224                            = methods[i].invoke(thr, (Object[]) null);
2250                       final String key
226                            = methods[i].getName().substring(
227                                GETTER_METHOD_PREFIX_LENGTH);
2280                       result.put(key, value);
2290                   }
2300                   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                    {
2360                       final Object value
237                            = methods[i].invoke(thr, (Object[]) null);
2380                       final String key
239                            = methods[i].getName().substring(
240                                BOOLEAN_GETTER_METHOD_PREFIX_LENGTH);
2410                       result.put(key, value);
242                    }
243                }
2440               catch (InvocationTargetException e)
245                {
246                    // Ignore this property, continue with next
247                }
2480               catch (IllegalArgumentException e)
249                {
250                    // Ignore this property, continue with next
251                }
2520               catch (IllegalAccessException e)
253                {
254                    // Ignore this property, continue with next
2550               }
256            }
257         }
2580        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     {
269100        final StringWriter sw = new StringWriter();
270100        PrintWriter pw = null;
271         try
272         {
273100            pw = new PrintWriter(sw);
274100            thr.printStackTrace(pw);
275         }
276         finally
277         {
27850            IoUtil.close(pw);
27950            IoUtil.close(sw);
280100        }
281100        return sw.toString();
282     }
283  
284     static Method findGetCauseMethod (final Method[] methods)
285     {
286100       Method theGetCauseMethod = null;
287100       for (int i = 0; i < methods.length; i++)
288        {
28975          if (methods[i].getDeclaringClass() == Throwable.class)
290           {
291100             continue;
292           }
293100          final int modifier = methods[i].getModifiers();
29485          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
301100(3)             if (methods[i].getName().equals("getCause"))
302               {
303100                  theGetCauseMethod = methods[i];
304100                  break;
305               }
3060             if (theGetCauseMethod != null)
307              {
308                 // 2nd hit, safety first
3090                logger.fine("Found 2 matching methods " + theGetCauseMethod
310                       + " or " + methods[i] + ".");
3110                theGetCauseMethod = null;
3120                break;
313              }
3140             theGetCauseMethod = methods[i];
315           }
316        }
317100       return theGetCauseMethod;
318     }
319  
320  
321     private static void initCause (Throwable current, Method theGetCauseMethod)
322     {
323        try
324        {
325100          final Throwable cause
326                 = (Throwable) theGetCauseMethod.invoke(
327                    current, EMPTY_ARRAY);
328100          INIT_CAUSE.invoke(current, new Object[] {cause});
329        }
330100       catch (Exception e)
331        {
332100          logger.log(Level.FINEST, "Failed to init cause for " + current
333                 + " using " + theGetCauseMethod + " got a exception.", e);
334100       }
335100    }
336  
337     private static void collectParameters (
338         Loggable loggable, Throwable thr, int nesting)
339         throws IllegalAccessException, InvocationTargetException
340     {
341100        final Method[] methods = thr.getClass().getMethods();
342100        for (int i = 0; i < methods.length; i++)
343         {
344100           final int modifier = methods[i].getModifiers();
34592           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            {
354100               final Object result = methods[i].invoke(thr, (Object[]) null);
355100               if (result != null && result != thr.getCause())
356                {
357100                   loggable.addParameter(
358                        "CAUSE_" + nesting + "_" + thr.getClass().getName()
359                            + "#" + methods[i].getName().substring(
360                                GETTER_METHOD_PREFIX_LENGTH),
361                        asString(result));
362                }
363            }
364         }
365100    }
366  
367      private static String asString (Object obj)
368      {
369          String result;
370100         if (obj instanceof Object[])
371          {
3720             result = ArraysUtil.toString((Object[]) obj);
373          }
374          else
375          {
376100             result = String.valueOf(obj);
377          }
378100         return StringUtil.trimLength(
379              result,
380              MAX_REASONABLE_PARAMETER_LENGTH);
381      }
382  }

Findings in this File

c (1) 205 : 4 Cyclomatic Complexity is 15 (max allowed is 12).
c (2) 205 : 55 '(' is not preceded with whitespace.
i (3) 301 : 0 method org.jcoderz.commons.util.ThrowableUtil.findGetCauseMethod(Method[]) makes literal string comparisons passing the literal as an argument