| // Copyright 2004, 2005 The Apache Software Foundation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package org.apache.tapestry.util.exception; |
| |
| import java.beans.BeanInfo; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.io.*; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Analyzes an exception, creating one or more {@link ExceptionDescription}s from it. |
| * |
| * @author Howard Lewis Ship |
| */ |
| |
| public class ExceptionAnalyzer |
| { |
| private static final int SKIP_LEADING_WHITESPACE = 0; |
| |
| private static final int SKIP_T = 1; |
| |
| private static final int SKIP_OTHER_WHITESPACE = 2; |
| |
| private final List exceptionDescriptions = new ArrayList(); |
| |
| private final List propertyDescriptions = new ArrayList(); |
| |
| private final CharArrayWriter writer = new CharArrayWriter(); |
| |
| private boolean exhaustive = false; |
| |
| /** |
| * If true, then stack trace is extracted for each exception. If false, the default, then stack |
| * trace is extracted for only the deepest exception. |
| */ |
| |
| public boolean isExhaustive() |
| { |
| return exhaustive; |
| } |
| |
| public void setExhaustive(boolean value) |
| { |
| exhaustive = value; |
| } |
| |
| /** |
| * Analyzes the exceptions. This builds an {@link ExceptionDescription}for the exception. It |
| * also looks for a non-null {@link Throwable}property. If one exists, then a second |
| * {@link ExceptionDescription}is created. This continues until no more nested exceptions can |
| * be found. |
| * <p> |
| * The description includes a set of name/value properties (as {@link ExceptionProperty}) |
| * object. This list contains all non-null properties that are not, themselves, |
| * {@link Throwable}. |
| * <p> |
| * The name is the display name (not the logical name) of the property. The value is the |
| * <code>toString()</code> value of the property. Only properties defined in subclasses of |
| * {@link Throwable}are included. |
| * <p> |
| * A future enhancement will be to alphabetically sort the properties by name. |
| */ |
| |
| public ExceptionDescription[] analyze(Throwable exception) |
| { |
| Throwable thrown = exception; |
| try |
| { |
| while (thrown != null) |
| { |
| thrown = buildDescription(thrown); |
| } |
| |
| ExceptionDescription[] result = new ExceptionDescription[exceptionDescriptions.size()]; |
| |
| return (ExceptionDescription[]) exceptionDescriptions.toArray(result); |
| } |
| finally |
| { |
| exceptionDescriptions.clear(); |
| propertyDescriptions.clear(); |
| |
| writer.reset(); |
| } |
| } |
| |
| protected Throwable buildDescription(Throwable exception) |
| { |
| propertyDescriptions.clear(); |
| |
| Class exceptionClass = exception.getClass(); |
| |
| // Get properties, ignoring those in Throwable and higher |
| // (including the 'message' property). |
| |
| BeanInfo info; |
| try |
| { |
| info = Introspector.getBeanInfo(exceptionClass, Throwable.class); |
| } |
| catch (IntrospectionException e) |
| { |
| return null; |
| } |
| |
| Object value; |
| Throwable next = null; |
| String message = exception.getMessage(); |
| PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); |
| |
| for (int i = 0; i < descriptors.length; i++) |
| { |
| PropertyDescriptor descriptor = descriptors[i]; |
| |
| Method method = descriptor.getReadMethod(); |
| if (method == null) |
| continue; |
| |
| try |
| { |
| value = method.invoke(exception, null); |
| } |
| catch (Exception e) |
| { |
| continue; |
| } |
| |
| if (value == null) |
| continue; |
| |
| // Some annoying exceptions duplicate the message property |
| // (I'm talking to YOU SAXParseException), so just edit that out. |
| |
| if (message != null && message.equals(value)) |
| continue; |
| |
| // Skip Throwables ... but the first non-null found is the next |
| // exception (unless it refers to the current one - some 3rd party |
| // libaries do this). We kind of count on there being no more |
| // than one Throwable property per Exception. |
| |
| if (value instanceof Throwable) |
| { |
| if (next == null && value != exception) |
| next = (Throwable) value; |
| |
| continue; |
| } |
| |
| String stringValue = value.toString(); |
| |
| if (stringValue == null) |
| continue; |
| |
| stringValue = stringValue.trim(); |
| |
| if (stringValue.length() == 0) |
| continue; |
| |
| ExceptionProperty property = new ExceptionProperty(descriptor.getDisplayName(), value); |
| |
| propertyDescriptions.add(property); |
| } |
| |
| // If exhaustive, or in the deepest exception (where there's no next) |
| // the extract the stack trace. |
| String[] stackTrace = null; |
| |
| if (next == null || exhaustive) |
| stackTrace = getStackTrace(exception); |
| |
| // Would be nice to sort the properties here. |
| |
| ExceptionProperty[] properties = new ExceptionProperty[propertyDescriptions.size()]; |
| |
| ExceptionProperty[] propArray = (ExceptionProperty[]) propertyDescriptions.toArray(properties); |
| |
| ExceptionDescription description = new ExceptionDescription(exceptionClass.getName(), message, propArray, stackTrace); |
| |
| exceptionDescriptions.add(description); |
| |
| return next; |
| } |
| |
| /** |
| * Gets the stack trace for the exception, and converts it into an array of strings. |
| * <p> |
| * This involves parsing the string generated indirectly from |
| * <code>Throwable.printStackTrace(PrintWriter)</code>. This method can get confused if the |
| * message (presumably, the first line emitted by printStackTrace()) spans multiple lines. |
| * <p> |
| * Different JVMs format the exception in different ways. |
| * <p> |
| * A possible expansion would be more flexibility in defining the pattern used. Hopefully all |
| * 'mainstream' JVMs are close enough for this to continue working. |
| */ |
| |
| protected String[] getStackTrace(Throwable exception) |
| { |
| writer.reset(); |
| |
| PrintWriter printWriter = new PrintWriter(writer); |
| |
| exception.printStackTrace(printWriter); |
| |
| printWriter.close(); |
| |
| String fullTrace = writer.toString(); |
| |
| writer.reset(); |
| |
| // OK, the trick is to convert the full trace into an array of stack frames. |
| |
| StringReader stringReader = new StringReader(fullTrace); |
| LineNumberReader lineReader = new LineNumberReader(stringReader); |
| int lineNumber = 0; |
| List frames = new ArrayList(); |
| |
| try |
| { |
| while (true) |
| { |
| String line = lineReader.readLine(); |
| |
| if (line == null) |
| break; |
| |
| // Always ignore the first line. |
| |
| if (++lineNumber == 1) |
| continue; |
| |
| frames.add(stripFrame(line)); |
| } |
| |
| lineReader.close(); |
| } |
| catch (IOException ex) |
| { |
| // Not likely to happen with this particular set |
| // of readers. |
| } |
| |
| String[] result = new String[frames.size()]; |
| |
| return (String[]) frames.toArray(result); |
| } |
| |
| /** |
| * Sun's JVM prefixes each line in the stack trace with " <tab>at</tab> ", other JVMs don't. This |
| * method looks for and strips such stuff. |
| */ |
| |
| private String stripFrame(String frame) |
| { |
| char[] array = frame.toCharArray(); |
| |
| int i = 0; |
| int state = SKIP_LEADING_WHITESPACE; |
| boolean more = true; |
| |
| while (more) |
| { |
| // Ran out of characters to skip? Return the empty string. |
| |
| if (i == array.length) |
| return ""; |
| |
| char ch = array[i]; |
| |
| switch (state) |
| { |
| // Ignore whitespace at the start of the line. |
| |
| case SKIP_LEADING_WHITESPACE: |
| |
| if (Character.isWhitespace(ch)) |
| { |
| i++; |
| continue; |
| } |
| |
| if (ch == 'a') |
| { |
| state = SKIP_T; |
| i++; |
| continue; |
| } |
| |
| // Found non-whitespace, not 'a' |
| more = false; |
| break; |
| |
| // Skip over the 't' after an 'a' |
| |
| case SKIP_T: |
| |
| if (ch == 't') |
| { |
| state = SKIP_OTHER_WHITESPACE; |
| i++; |
| continue; |
| } |
| |
| // Back out the skipped-over 'a' |
| |
| i--; |
| more = false; |
| break; |
| |
| // Skip whitespace between 'at' and the name of the class |
| |
| case SKIP_OTHER_WHITESPACE: |
| |
| if (Character.isWhitespace(ch)) |
| { |
| i++; |
| continue; |
| } |
| |
| // Not whitespace |
| more = false; |
| break; |
| } |
| |
| } |
| |
| // Found nothing to strip out. |
| |
| if (i == 0) |
| return frame; |
| |
| return frame.substring(i); |
| } |
| |
| /** |
| * Produces a text based exception report to the provided stream. |
| */ |
| |
| public void reportException(Throwable exception, PrintStream stream) |
| { |
| int i; |
| int j; |
| ExceptionDescription[] descriptions; |
| ExceptionProperty[] properties; |
| String[] stackTrace; |
| String message; |
| |
| descriptions = analyze(exception); |
| |
| for (i = 0; i < descriptions.length; i++) |
| { |
| message = descriptions[i].getMessage(); |
| |
| if (message == null) |
| stream.println(descriptions[i].getExceptionClassName()); |
| else |
| stream.println(descriptions[i].getExceptionClassName() + ": " |
| + descriptions[i].getMessage()); |
| |
| properties = descriptions[i].getProperties(); |
| |
| for (j = 0; j < properties.length; j++) |
| stream.println(" " + properties[j].getName() + ": " + properties[j].getValue()); |
| |
| // Just show the stack trace on the deepest exception. |
| |
| if (i + 1 == descriptions.length) |
| { |
| stackTrace = descriptions[i].getStackTrace(); |
| |
| for (j = 0; j < stackTrace.length; j++) |
| stream.println(stackTrace[j]); |
| } |
| else |
| { |
| stream.println(); |
| } |
| } |
| } |
| |
| } |