Clover coverage report - Code Coverage for tapestry release 4.0-beta-12
Coverage timestamp: Sun Oct 30 2005 16:22:01 EST
file stats: LOC: 409   Methods: 7
NCLOC: 229   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ExceptionAnalyzer.java 80.4% 88.2% 71.4% 85.7%
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  23 public ExceptionDescription[] analyze(Throwable exception)
 80    {
 81  23 try
 82    {
 83   
 84  23 while (exception != null)
 85    {
 86  30 exception = buildDescription(exception);
 87    }
 88   
 89  23 ExceptionDescription[] result = new ExceptionDescription[exceptionDescriptions.size()];
 90   
 91  23 return (ExceptionDescription[]) exceptionDescriptions.toArray(result);
 92    }
 93    finally
 94    {
 95  23 exceptionDescriptions.clear();
 96  23 propertyDescriptions.clear();
 97   
 98  23 writer.reset();
 99    }
 100    }
 101   
 102  30 protected Throwable buildDescription(Throwable exception)
 103    {
 104  30 BeanInfo info;
 105  30 Class exceptionClass;
 106  30 ExceptionProperty property;
 107  30 PropertyDescriptor[] descriptors;
 108  30 PropertyDescriptor descriptor;
 109  30 Throwable next = null;
 110  30 int i;
 111  30 Object value;
 112  30 Method method;
 113  30 ExceptionProperty[] properties;
 114  30 ExceptionDescription description;
 115  30 String stringValue;
 116  30 String message;
 117  30 String[] stackTrace = null;
 118   
 119  30 propertyDescriptions.clear();
 120   
 121  30 message = exception.getMessage();
 122  30 exceptionClass = exception.getClass();
 123   
 124    // Get properties, ignoring those in Throwable and higher
 125    // (including the 'message' property).
 126   
 127  30 try
 128    {
 129  30 info = Introspector.getBeanInfo(exceptionClass, Throwable.class);
 130    }
 131    catch (IntrospectionException e)
 132    {
 133  0 return null;
 134    }
 135   
 136  30 descriptors = info.getPropertyDescriptors();
 137   
 138  30 for (i = 0; i < descriptors.length; i++)
 139    {
 140  111 descriptor = descriptors[i];
 141   
 142  111 method = descriptor.getReadMethod();
 143  111 if (method == null)
 144  0 continue;
 145   
 146  111 try
 147    {
 148  111 value = method.invoke(exception, null);
 149    }
 150    catch (Exception e)
 151    {
 152  0 continue;
 153    }
 154   
 155  111 if (value == null)
 156  58 continue;
 157   
 158    // Some annoying exceptions duplicate the message property
 159    // (I'm talking to YOU SAXParseException), so just edit that out.
 160   
 161  53 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  53 if (value instanceof Throwable)
 170    {
 171  13 if (next == null)
 172  7 next = (Throwable) value;
 173   
 174  13 continue;
 175    }
 176   
 177  40 stringValue = value.toString().trim();
 178   
 179  40 if (stringValue.length() == 0)
 180  0 continue;
 181   
 182  40 property = new ExceptionProperty(descriptor.getDisplayName(), value);
 183   
 184  40 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  30 if (next == null || exhaustive)
 191  23 stackTrace = getStackTrace(exception);
 192   
 193    // Would be nice to sort the properties here.
 194   
 195  30 properties = new ExceptionProperty[propertyDescriptions.size()];
 196   
 197  30 ExceptionProperty[] propArray = (ExceptionProperty[]) propertyDescriptions
 198    .toArray(properties);
 199   
 200  30 description = new ExceptionDescription(exceptionClass.getName(), message, propArray,
 201    stackTrace);
 202   
 203  30 exceptionDescriptions.add(description);
 204   
 205  30 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  23 protected String[] getStackTrace(Throwable exception)
 222    {
 223  23 writer.reset();
 224   
 225  23 PrintWriter printWriter = new PrintWriter(writer);
 226   
 227  23 exception.printStackTrace(printWriter);
 228   
 229  23 printWriter.close();
 230   
 231  23 String fullTrace = writer.toString();
 232   
 233  23 writer.reset();
 234   
 235    // OK, the trick is to convert the full trace into an array of stack frames.
 236   
 237  23 StringReader stringReader = new StringReader(fullTrace);
 238  23 LineNumberReader lineReader = new LineNumberReader(stringReader);
 239  23 int lineNumber = 0;
 240  23 List frames = new ArrayList();
 241   
 242  23 try
 243    {
 244  23 while (true)
 245    {
 246  1353 String line = lineReader.readLine();
 247   
 248  1353 if (line == null)
 249  23 break;
 250   
 251    // Always ignore the first line.
 252   
 253  1330 if (++lineNumber == 1)
 254  23 continue;
 255   
 256  1307 frames.add(stripFrame(line));
 257    }
 258   
 259  23 lineReader.close();
 260    }
 261    catch (IOException ex)
 262    {
 263    // Not likely to happen with this particular set
 264    // of readers.
 265    }
 266   
 267  23 String result[] = new String[frames.size()];
 268   
 269  23 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  1307 private String stripFrame(String frame)
 284    {
 285  1307 char array[] = frame.toCharArray();
 286   
 287  1307 int i = 0;
 288  1307 int state = SKIP_LEADING_WHITESPACE;
 289  1307 boolean more = true;
 290   
 291  1307 while (more)
 292    {
 293    // Ran out of characters to skip? Return the empty string.
 294   
 295  6535 if (i == array.length)
 296  0 return "";
 297   
 298  6535 char ch = array[i];
 299   
 300  6535 switch (state)
 301    {
 302    // Ignore whitespace at the start of the line.
 303   
 304  2614 case SKIP_LEADING_WHITESPACE:
 305   
 306  2614 if (Character.isWhitespace(ch))
 307    {
 308  1307 i++;
 309  1307 continue;
 310    }
 311   
 312  1307 if (ch == 'a')
 313    {
 314  1307 state = SKIP_T;
 315  1307 i++;
 316  1307 continue;
 317    }
 318   
 319    // Found non-whitespace, not 'a'
 320  0 more = false;
 321  0 break;
 322   
 323    // Skip over the 't' after an 'a'
 324   
 325  1307 case SKIP_T:
 326   
 327  1307 if (ch == 't')
 328    {
 329  1307 state = SKIP_OTHER_WHITESPACE;
 330  1307 i++;
 331  1307 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  2614 case SKIP_OTHER_WHITESPACE:
 343   
 344  2614 if (Character.isWhitespace(ch))
 345    {
 346  1307 i++;
 347  1307 continue;
 348    }
 349   
 350    // Not whitespace
 351  1307 more = false;
 352  1307 break;
 353    }
 354   
 355    }
 356   
 357    // Found nothing to strip out.
 358   
 359  1307 if (i == 0)
 360  0 return frame;
 361   
 362  1307 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    }