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