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