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