source: josm/trunk/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java@ 11746

Last change on this file since 11746 was 11746, checked in by Don-vip, 7 years ago

PMD - Strict Exceptions

  • Property svn:eol-style set to native
File size: 11.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools.bugreport;
3
4import java.io.PrintWriter;
5import java.io.Serializable;
6import java.lang.reflect.InvocationTargetException;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collection;
10import java.util.Collections;
11import java.util.ConcurrentModificationException;
12import java.util.IdentityHashMap;
13import java.util.Iterator;
14import java.util.LinkedList;
15import java.util.Map;
16import java.util.Map.Entry;
17import java.util.NoSuchElementException;
18import java.util.Set;
19import java.util.function.Supplier;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.tools.StreamUtils;
23
24/**
25 * This is a special exception that cannot be directly thrown.
26 * <p>
27 * It is used to capture more information about an exception that was already thrown.
28 *
29 * @author Michael Zangl
30 * @see BugReport
31 * @since 10285
32 */
33public class ReportedException extends RuntimeException {
34 /**
35 * How many entries of a collection to include in the bug report.
36 */
37 private static final int MAX_COLLECTION_ENTRIES = 30;
38
39 private static final long serialVersionUID = 737333873766201033L;
40
41 /**
42 * We capture all stack traces on exception creation. This allows us to trace synchonization problems better. We cannot be really sure what
43 * happened but we at least see which threads
44 */
45 private final transient Map<Thread, StackTraceElement[]> allStackTraces;
46 private final LinkedList<Section> sections = new LinkedList<>();
47 private final transient Thread caughtOnThread;
48 private String methodWarningFrom;
49
50 ReportedException(Throwable exception) {
51 this(exception, Thread.currentThread());
52 }
53
54 ReportedException(Throwable exception, Thread caughtOnThread) {
55 super(exception);
56
57 allStackTraces = Thread.getAllStackTraces();
58 this.caughtOnThread = caughtOnThread;
59 }
60
61 /**
62 * Displays a warning for this exception. The program can then continue normally. Does not block.
63 */
64 public void warn() {
65 methodWarningFrom = BugReport.getCallingMethod(2);
66 try {
67 BugReportQueue.getInstance().submit(this);
68 } catch (RuntimeException e) { // NOPMD
69 e.printStackTrace();
70 }
71 }
72
73 /**
74 * Starts a new debug data section. This normally does not need to be called manually.
75 *
76 * @param sectionName
77 * The section name.
78 */
79 public void startSection(String sectionName) {
80 sections.add(new Section(sectionName));
81 }
82
83 /**
84 * Prints the captured data of this report to a {@link PrintWriter}.
85 *
86 * @param out
87 * The writer to print to.
88 */
89 public void printReportDataTo(PrintWriter out) {
90 out.println("=== REPORTED CRASH DATA ===");
91 for (Section s : sections) {
92 s.printSection(out);
93 out.println();
94 }
95
96 if (methodWarningFrom != null) {
97 out.println("Warning issued by: " + methodWarningFrom);
98 out.println();
99 }
100 }
101
102 /**
103 * Prints the stack trace of this report to a {@link PrintWriter}.
104 *
105 * @param out
106 * The writer to print to.
107 */
108 public void printReportStackTo(PrintWriter out) {
109 out.println("=== STACK TRACE ===");
110 out.println(niceThreadName(caughtOnThread));
111 getCause().printStackTrace(out);
112 out.println();
113 }
114
115 /**
116 * Prints the stack traces for other threads of this report to a {@link PrintWriter}.
117 *
118 * @param out
119 * The writer to print to.
120 */
121 public void printReportThreadsTo(PrintWriter out) {
122 out.println("=== RUNNING THREADS ===");
123 for (Entry<Thread, StackTraceElement[]> thread : allStackTraces.entrySet()) {
124 out.println(niceThreadName(thread.getKey()));
125 if (caughtOnThread.equals(thread.getKey())) {
126 out.println("Stacktrace see above.");
127 } else {
128 for (StackTraceElement e : thread.getValue()) {
129 out.println(e);
130 }
131 }
132 out.println();
133 }
134 }
135
136 private static String niceThreadName(Thread thread) {
137 StringBuilder name = new StringBuilder("Thread: ").append(thread.getName()).append(" (").append(thread.getId()).append(')');
138 ThreadGroup threadGroup = thread.getThreadGroup();
139 if (threadGroup != null) {
140 name.append(" of ").append(threadGroup.getName());
141 }
142 return name.toString();
143 }
144
145 /**
146 * Checks if this exception is considered the same as an other exception. This is the case if both have the same cause and message.
147 *
148 * @param e
149 * The exception to check against.
150 * @return <code>true</code> if they are considered the same.
151 */
152 public boolean isSame(ReportedException e) {
153 if (!getMessage().equals(e.getMessage())) {
154 return false;
155 }
156
157 return hasSameStackTrace(new CauseTraceIterator(), e.getCause());
158 }
159
160 private static boolean hasSameStackTrace(CauseTraceIterator causeTraceIterator, Throwable e2) {
161 if (!causeTraceIterator.hasNext()) {
162 // all done.
163 return true;
164 }
165 Throwable e1 = causeTraceIterator.next();
166 StackTraceElement[] t1 = e1.getStackTrace();
167 StackTraceElement[] t2 = e2.getStackTrace();
168
169 if (!Arrays.equals(t1, t2)) {
170 return false;
171 }
172
173 Throwable c1 = e1.getCause();
174 Throwable c2 = e2.getCause();
175 if ((c1 == null) != (c2 == null)) {
176 return false;
177 } else if (c1 != null) {
178 return hasSameStackTrace(causeTraceIterator, c2);
179 } else {
180 return true;
181 }
182 }
183
184 /**
185 * Adds some debug values to this exception. The value is converted to a string. Errors during conversion are handled.
186 *
187 * @param key
188 * The key to add this for. Does not need to be unique but it would be nice.
189 * @param value
190 * The value.
191 * @return This exception for easy chaining.
192 */
193 public ReportedException put(String key, Object value) {
194 return put(key, () -> value);
195 }
196
197 /**
198 * Adds some debug values to this exception. This method automatically catches errors that occur during the production of the value.
199 *
200 * @param key
201 * The key to add this for. Does not need to be unique but it would be nice.
202 * @param valueSupplier
203 * A supplier that is called once to get the value.
204 * @return This exception for easy chaining.
205 * @since 10586
206 */
207 public ReportedException put(String key, Supplier<Object> valueSupplier) {
208 String string;
209 try {
210 Object value = valueSupplier.get();
211 if (value == null) {
212 string = "null";
213 } else if (value instanceof Collection) {
214 string = makeCollectionNice((Collection<?>) value);
215 } else if (value.getClass().isArray()) {
216 string = makeCollectionNice(Arrays.asList(value));
217 } else {
218 string = value.toString();
219 }
220 } catch (RuntimeException t) { // NOPMD
221 Main.warn(t);
222 string = "<Error calling toString()>";
223 }
224 sections.getLast().put(key, string);
225 return this;
226 }
227
228 private static String makeCollectionNice(Collection<?> value) {
229 int lines = 0;
230 StringBuilder str = new StringBuilder(32);
231 for (Object e : value) {
232 str.append("\n - ");
233 if (lines <= MAX_COLLECTION_ENTRIES) {
234 str.append(e);
235 } else {
236 str.append("\n ... (")
237 .append(value.size())
238 .append(" entries)");
239 break;
240 }
241 }
242 return str.toString();
243 }
244
245 @Override
246 public String toString() {
247 return "ReportedException [thread=" + caughtOnThread + ", exception=" + getCause()
248 + ", methodWarningFrom=" + methodWarningFrom + ']';
249 }
250
251 /**
252 * Check if this exception may be caused by a threading issue.
253 * @return <code>true</code> if it is.
254 * @since 10585
255 */
256 public boolean mayHaveConcurrentSource() {
257 return StreamUtils.toStream(CauseTraceIterator::new)
258 .anyMatch(t -> t instanceof ConcurrentModificationException || t instanceof InvocationTargetException);
259 }
260
261 /**
262 * Check if this is caused by an out of memory situaition
263 * @return <code>true</code> if it is.
264 * @since 10819
265 */
266 public boolean isOutOfMemory() {
267 return StreamUtils.toStream(CauseTraceIterator::new).anyMatch(t -> t instanceof OutOfMemoryError);
268 }
269
270 /**
271 * Iterates over the causes for this exception. Ignores cycles and aborts iteration then.
272 * @author Michal Zangl
273 * @since 10585
274 */
275 private final class CauseTraceIterator implements Iterator<Throwable> {
276 private Throwable current = getCause();
277 private final Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
278
279 @Override
280 public boolean hasNext() {
281 return current != null;
282 }
283
284 @Override
285 public Throwable next() {
286 if (!hasNext()) {
287 throw new NoSuchElementException();
288 }
289 Throwable toReturn = current;
290 advance();
291 return toReturn;
292 }
293
294 private void advance() {
295 dejaVu.add(current);
296 current = current.getCause();
297 if (current != null && dejaVu.contains(current)) {
298 current = null;
299 }
300 }
301 }
302
303 private static class SectionEntry implements Serializable {
304
305 private static final long serialVersionUID = 1L;
306
307 private final String key;
308 private final String value;
309
310 SectionEntry(String key, String value) {
311 this.key = key;
312 this.value = value;
313 }
314
315 /**
316 * Prints this entry to the output stream in a line.
317 * @param out The stream to print to.
318 */
319 public void print(PrintWriter out) {
320 out.print(" - ");
321 out.print(key);
322 out.print(": ");
323 out.println(value);
324 }
325 }
326
327 private static class Section implements Serializable {
328
329 private static final long serialVersionUID = 1L;
330
331 private final String sectionName;
332 private final ArrayList<SectionEntry> entries = new ArrayList<>();
333
334 Section(String sectionName) {
335 this.sectionName = sectionName;
336 }
337
338 /**
339 * Add a key/value entry to this section.
340 * @param key The key. Need not be unique.
341 * @param value The value.
342 */
343 public void put(String key, String value) {
344 entries.add(new SectionEntry(key, value));
345 }
346
347 /**
348 * Prints this section to the output stream.
349 * @param out The stream to print to.
350 */
351 public void printSection(PrintWriter out) {
352 out.println(sectionName + ':');
353 if (entries.isEmpty()) {
354 out.println("No data collected.");
355 } else {
356 for (SectionEntry e : entries) {
357 e.print(out);
358 }
359 }
360 }
361 }
362}
Note: See TracBrowser for help on using the repository browser.