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