Coverage Report - org.apache.tapestry.util.exception.ExceptionAnalyzer
 
Classes in this File Line Coverage Branch Coverage Complexity
ExceptionAnalyzer
0% 
0% 
5.857
 
 1  
 // Copyright 2004, 2005 The Apache Software Foundation
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package org.apache.tapestry.util.exception;
 16  
 
 17  
 import java.beans.BeanInfo;
 18  
 import java.beans.IntrospectionException;
 19  
 import java.beans.Introspector;
 20  
 import java.beans.PropertyDescriptor;
 21  
 import java.io.CharArrayWriter;
 22  
 import java.io.IOException;
 23  
 import java.io.LineNumberReader;
 24  
 import java.io.PrintStream;
 25  
 import java.io.PrintWriter;
 26  
 import java.io.StringReader;
 27  
 import java.lang.reflect.Method;
 28  
 import java.util.ArrayList;
 29  
 import java.util.List;
 30  
 
 31  
 /**
 32  
  * Analyzes an exception, creating one or more {@link ExceptionDescription}s from it.
 33  
  * 
 34  
  * @author Howard Lewis Ship
 35  
  */
 36  
 
 37  0
 public class ExceptionAnalyzer
 38  
 {
 39  
     private static final int SKIP_LEADING_WHITESPACE = 0;
 40  
 
 41  
     private static final int SKIP_T = 1;
 42  
 
 43  
     private static final int SKIP_OTHER_WHITESPACE = 2;
 44  
     
 45  0
     private final List exceptionDescriptions = new ArrayList();
 46  
 
 47  0
     private final List propertyDescriptions = new ArrayList();
 48  
 
 49  0
     private final CharArrayWriter writer = new CharArrayWriter();
 50  
 
 51  0
     private boolean exhaustive = false;
 52  
 
 53  
     /**
 54  
      * If true, then stack trace is extracted for each exception. If false, the default, then stack
 55  
      * trace is extracted for only the deepest exception.
 56  
      */
 57  
 
 58  
     public boolean isExhaustive()
 59  
     {
 60  0
         return exhaustive;
 61  
     }
 62  
 
 63  
     public void setExhaustive(boolean value)
 64  
     {
 65  0
         exhaustive = value;
 66  0
     }
 67  
 
 68  
     /**
 69  
      * Analyzes the exceptions. This builds an {@link ExceptionDescription}for the exception. It
 70  
      * also looks for a non-null {@link Throwable}property. If one exists, then a second
 71  
      * {@link ExceptionDescription}is created. This continues until no more nested exceptions can
 72  
      * be found.
 73  
      * <p>
 74  
      * The description includes a set of name/value properties (as {@link ExceptionProperty})
 75  
      * object. This list contains all non-null properties that are not, themselves,
 76  
      * {@link Throwable}.
 77  
      * <p>
 78  
      * The name is the display name (not the logical name) of the property. The value is the
 79  
      * <code>toString()</code> value of the property. Only properties defined in subclasses of
 80  
      * {@link Throwable}are included.
 81  
      * <p>
 82  
      * A future enhancement will be to alphabetically sort the properties by name.
 83  
      */
 84  
 
 85  
     public ExceptionDescription[] analyze(Throwable exception)
 86  
     {
 87  0
         Throwable thrown = exception;
 88  
         try
 89  
         {
 90  
 
 91  0
             while (thrown != null)
 92  
             {
 93  0
                 thrown = buildDescription(thrown);
 94  
             }
 95  
 
 96  0
             ExceptionDescription[] result = new ExceptionDescription[exceptionDescriptions.size()];
 97  
 
 98  0
             return (ExceptionDescription[]) exceptionDescriptions.toArray(result);
 99  
         }
 100  
         finally
 101  
         {
 102  0
             exceptionDescriptions.clear();
 103  0
             propertyDescriptions.clear();
 104  
 
 105  0
             writer.reset();
 106  
         }
 107  
     }
 108  
 
 109  
     protected Throwable buildDescription(Throwable exception)
 110  
     {
 111  
         BeanInfo info;
 112  
         Class exceptionClass;
 113  
         ExceptionProperty property;
 114  
         PropertyDescriptor[] descriptors;
 115  
         PropertyDescriptor descriptor;
 116  0
         Throwable next = null;
 117  
         int i;
 118  
         Object value;
 119  
         Method method;
 120  
         ExceptionProperty[] properties;
 121  
         ExceptionDescription description;
 122  
         String stringValue;
 123  
         String message;
 124  0
         String[] stackTrace = null;
 125  
 
 126  0
         propertyDescriptions.clear();
 127  
 
 128  0
         message = exception.getMessage();
 129  0
         exceptionClass = exception.getClass();
 130  
 
 131  
         // Get properties, ignoring those in Throwable and higher
 132  
         // (including the 'message' property).
 133  
 
 134  
         try
 135  
         {
 136  0
             info = Introspector.getBeanInfo(exceptionClass, Throwable.class);
 137  
         }
 138  0
         catch (IntrospectionException e)
 139  
         {
 140  0
             return null;
 141  0
         }
 142  
 
 143  0
         descriptors = info.getPropertyDescriptors();
 144  
 
 145  0
         for (i = 0; i < descriptors.length; i++)
 146  
         {
 147  0
             descriptor = descriptors[i];
 148  
 
 149  0
             method = descriptor.getReadMethod();
 150  0
             if (method == null)
 151  0
                 continue;
 152  
 
 153  
             try
 154  
             {
 155  0
                 value = method.invoke(exception, null);
 156  
             }
 157  0
             catch (Exception e)
 158  
             {
 159  0
                 continue;
 160  0
             }
 161  
 
 162  0
             if (value == null)
 163  0
                 continue;
 164  
 
 165  
             // Some annoying exceptions duplicate the message property
 166  
             // (I'm talking to YOU SAXParseException), so just edit that out.
 167  
 
 168  0
             if (message != null && message.equals(value))
 169  0
                 continue;
 170  
 
 171  
             // Skip Throwables ... but the first non-null found is the next 
 172  
             // exception (unless it refers to the current one - some 3rd party
 173  
             // libaries do this). We kind of count on there being no more 
 174  
             // than one Throwable property per Exception.
 175  
 
 176  0
             if (value instanceof Throwable)
 177  
             {
 178  0
                 if (next == null && value != exception)
 179  0
                     next = (Throwable) value;
 180  
 
 181  
                 continue;
 182  
             }
 183  
 
 184  0
             stringValue = value.toString().trim();
 185  
 
 186  0
             if (stringValue.length() == 0)
 187  0
                 continue;
 188  
 
 189  0
             property = new ExceptionProperty(descriptor.getDisplayName(), value);
 190  
 
 191  0
             propertyDescriptions.add(property);
 192  
         }
 193  
 
 194  
         // If exhaustive, or in the deepest exception (where there's no next)
 195  
         // the extract the stack trace.
 196  
 
 197  0
         if (next == null || exhaustive)
 198  0
             stackTrace = getStackTrace(exception);
 199  
 
 200  
         // Would be nice to sort the properties here.
 201  
 
 202  0
         properties = new ExceptionProperty[propertyDescriptions.size()];
 203  
 
 204  0
         ExceptionProperty[] propArray = (ExceptionProperty[]) propertyDescriptions
 205  
                 .toArray(properties);
 206  
 
 207  0
         description = new ExceptionDescription(exceptionClass.getName(), message, propArray,
 208  
                 stackTrace);
 209  
 
 210  0
         exceptionDescriptions.add(description);
 211  
 
 212  0
         return next;
 213  
     }
 214  
 
 215  
     /**
 216  
      * Gets the stack trace for the exception, and converts it into an array of strings.
 217  
      * <p>
 218  
      * This involves parsing the string generated indirectly from
 219  
      * <code>Throwable.printStackTrace(PrintWriter)</code>. This method can get confused if the
 220  
      * message (presumably, the first line emitted by printStackTrace()) spans multiple lines.
 221  
      * <p>
 222  
      * Different JVMs format the exception in different ways.
 223  
      * <p>
 224  
      * A possible expansion would be more flexibility in defining the pattern used. Hopefully all
 225  
      * 'mainstream' JVMs are close enough for this to continue working.
 226  
      */
 227  
 
 228  
     protected String[] getStackTrace(Throwable exception)
 229  
     {
 230  0
         writer.reset();
 231  
 
 232  0
         PrintWriter printWriter = new PrintWriter(writer);
 233  
 
 234  0
         exception.printStackTrace(printWriter);
 235  
 
 236  0
         printWriter.close();
 237  
 
 238  0
         String fullTrace = writer.toString();
 239  
 
 240  0
         writer.reset();
 241  
 
 242  
         // OK, the trick is to convert the full trace into an array of stack frames.
 243  
 
 244  0
         StringReader stringReader = new StringReader(fullTrace);
 245  0
         LineNumberReader lineReader = new LineNumberReader(stringReader);
 246  0
         int lineNumber = 0;
 247  0
         List frames = new ArrayList();
 248  
 
 249  
         try
 250  
         {
 251  
             while (true)
 252  
             {
 253  0
                 String line = lineReader.readLine();
 254  
 
 255  0
                 if (line == null)
 256  0
                     break;
 257  
 
 258  
                 // Always ignore the first line.
 259  
 
 260  0
                 if (++lineNumber == 1)
 261  0
                     continue;
 262  
 
 263  0
                 frames.add(stripFrame(line));
 264  0
             }
 265  
 
 266  0
             lineReader.close();
 267  
         }
 268  0
         catch (IOException ex)
 269  
         {
 270  
             // Not likely to happen with this particular set
 271  
             // of readers.
 272  0
         }
 273  
 
 274  0
         String[] result = new String[frames.size()];
 275  
 
 276  0
         return (String[]) frames.toArray(result);
 277  
     }
 278  
 
 279  
     /**
 280  
      * Sun's JVM prefixes each line in the stack trace with " <tab>at</tab> ", other JVMs don't. This
 281  
      * method looks for and strips such stuff.
 282  
      */
 283  
 
 284  
     private String stripFrame(String frame)
 285  
     {
 286  0
         char[] array = frame.toCharArray();
 287  
 
 288  0
         int i = 0;
 289  0
         int state = SKIP_LEADING_WHITESPACE;
 290  0
         boolean more = true;
 291  
 
 292  0
         while (more)
 293  
         {
 294  
             // Ran out of characters to skip? Return the empty string.
 295  
 
 296  0
             if (i == array.length)
 297  0
                 return "";
 298  
 
 299  0
             char ch = array[i];
 300  
 
 301  0
             switch (state)
 302  
             {
 303  
                 // Ignore whitespace at the start of the line.
 304  
 
 305  
                 case SKIP_LEADING_WHITESPACE:
 306  
 
 307  0
                     if (Character.isWhitespace(ch))
 308  
                     {
 309  0
                         i++;
 310  0
                         continue;
 311  
                     }
 312  
 
 313  0
                     if (ch == 'a')
 314  
                     {
 315  0
                         state = SKIP_T;
 316  0
                         i++;
 317  0
                         continue;
 318  
                     }
 319  
 
 320  
                     // Found non-whitespace, not 'a'
 321  0
                     more = false;
 322  0
                     break;
 323  
 
 324  
                 // Skip over the 't' after an 'a'
 325  
 
 326  
                 case SKIP_T:
 327  
 
 328  0
                     if (ch == 't')
 329  
                     {
 330  0
                         state = SKIP_OTHER_WHITESPACE;
 331  0
                         i++;
 332  0
                         continue;
 333  
                     }
 334  
 
 335  
                     // Back out the skipped-over 'a'
 336  
 
 337  0
                     i--;
 338  0
                     more = false;
 339  0
                     break;
 340  
 
 341  
                 // Skip whitespace between 'at' and the name of the class
 342  
 
 343  
                 case SKIP_OTHER_WHITESPACE:
 344  
 
 345  0
                     if (Character.isWhitespace(ch))
 346  
                     {
 347  0
                         i++;
 348  0
                         continue;
 349  
                     }
 350  
 
 351  
                     // Not whitespace
 352  0
                     more = false;
 353  
                     break;
 354  
             }
 355  
 
 356  0
         }
 357  
 
 358  
         // Found nothing to strip out.
 359  
 
 360  0
         if (i == 0)
 361  0
             return frame;
 362  
 
 363  0
         return frame.substring(i);
 364  
     }
 365  
 
 366  
     /**
 367  
      * Produces a text based exception report to the provided stream.
 368  
      */
 369  
 
 370  
     public void reportException(Throwable exception, PrintStream stream)
 371  
     {
 372  
         int i;
 373  
         int j;
 374  
         ExceptionDescription[] descriptions;
 375  
         ExceptionProperty[] properties;
 376  
         String[] stackTrace;
 377  
         String message;
 378  
 
 379  0
         descriptions = analyze(exception);
 380  
 
 381  0
         for (i = 0; i < descriptions.length; i++)
 382  
         {
 383  0
             message = descriptions[i].getMessage();
 384  
 
 385  0
             if (message == null)
 386  0
                 stream.println(descriptions[i].getExceptionClassName());
 387  
             else
 388  0
                 stream.println(descriptions[i].getExceptionClassName() + ": "
 389  
                         + descriptions[i].getMessage());
 390  
 
 391  0
             properties = descriptions[i].getProperties();
 392  
 
 393  0
             for (j = 0; j < properties.length; j++)
 394  0
                 stream.println("   " + properties[j].getName() + ": " + properties[j].getValue());
 395  
 
 396  
             // Just show the stack trace on the deepest exception.
 397  
 
 398  0
             if (i + 1 == descriptions.length)
 399  
             {
 400  0
                 stackTrace = descriptions[i].getStackTrace();
 401  
 
 402  0
                 for (j = 0; j < stackTrace.length; j++)
 403  0
                     stream.println(stackTrace[j]);
 404  
             }
 405  
             else
 406  0
                 stream.println();
 407  
         }
 408  0
     }
 409  
 
 410  
 }