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