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

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

fix error_prone warnings

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