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

Last change on this file since 10597 was 10586, checked in by Don-vip, 8 years ago

see #11390, fix #13190 - Allow Lambda bug report parameters (patch by michael2402) - gsoc-core

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