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