source: josm/trunk/src/org/openstreetmap/josm/tools/bugreport/BugReport.java@ 12649

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

see #15182 - code refactoring to avoid dependence on GUI packages from Preferences

  • Property svn:eol-style set to native
File size: 7.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.io.StringWriter;
7import java.util.concurrent.CopyOnWriteArrayList;
8import java.util.function.Predicate;
9
10import org.openstreetmap.josm.actions.ShowStatusReportAction;
11
12/**
13 * This class contains utility methods to create and handle a bug report.
14 * <p>
15 * It allows you to configure the format and request to send the bug report.
16 * <p>
17 * It also contains the main entry point for all components to use the bug report system: Call {@link #intercept(Throwable)} to start handling an
18 * exception.
19 * <h1> Handling Exceptions </h1>
20 * In your code, you should add try...catch blocks for any runtime exceptions that might happen. It is fine to catch throwable there.
21 * <p>
22 * You should then add some debug information there. This can be the OSM ids that caused the error, information on the data you were working on
23 * or other local variables. Make sure that no excpetions may occur while computing the values. It is best to send plain local variables to
24 * put(...). If you need to do computations, put them into a lambda expression. Then simply throw the throwable you got from the bug report.
25 * The global exception handler will do the rest.
26 * <pre>
27 * int id = ...;
28 * String tag = "...";
29 * try {
30 * ... your code ...
31 * } catch (RuntimeException t) {
32 * throw BugReport.intercept(t).put("id", id).put("tag", () -&gt; x.getTag());
33 * }
34 * </pre>
35 *
36 * Instead of re-throwing, you can call {@link ReportedException#warn()}. This will display a warning to the user and allow it to either report
37 * the exception or ignore it.
38 *
39 * @author Michael Zangl
40 * @since 10285
41 */
42public final class BugReport implements Serializable {
43 private static final long serialVersionUID = 1L;
44
45 private boolean includeStatusReport = true;
46 private boolean includeData = true;
47 private boolean includeAllStackTraces;
48 private final ReportedException exception;
49 private final CopyOnWriteArrayList<BugReportListener> listeners = new CopyOnWriteArrayList<>();
50
51 /**
52 * Create a new bug report
53 * @param e The {@link ReportedException} to use. No more data should be added after creating the report.
54 */
55 public BugReport(ReportedException e) {
56 this.exception = e;
57 includeAllStackTraces = e.mayHaveConcurrentSource();
58 }
59
60 /**
61 * Determines if this report should include a system status report
62 * @return <code>true</code> to include it.
63 * @since 10597
64 */
65 public boolean isIncludeStatusReport() {
66 return includeStatusReport;
67 }
68
69 /**
70 * Set if this report should include a system status report
71 * @param includeStatusReport if the status report should be included
72 * @since 10585
73 */
74 public void setIncludeStatusReport(boolean includeStatusReport) {
75 this.includeStatusReport = includeStatusReport;
76 fireChange();
77 }
78
79 /**
80 * Determines if this report should include the data that was traced.
81 * @return <code>true</code> to include it.
82 * @since 10597
83 */
84 public boolean isIncludeData() {
85 return includeData;
86 }
87
88 /**
89 * Set if this report should include the data that was traced.
90 * @param includeData if data should be included
91 * @since 10585
92 */
93 public void setIncludeData(boolean includeData) {
94 this.includeData = includeData;
95 fireChange();
96 }
97
98 /**
99 * Determines if this report should include the stack traces for all other threads.
100 * @return <code>true</code> to include it.
101 * @since 10597
102 */
103 public boolean isIncludeAllStackTraces() {
104 return includeAllStackTraces;
105 }
106
107 /**
108 * Sets if this report should include the stack traces for all other threads.
109 * @param includeAllStackTraces if all stack traces should be included
110 * @since 10585
111 */
112 public void setIncludeAllStackTraces(boolean includeAllStackTraces) {
113 this.includeAllStackTraces = includeAllStackTraces;
114 fireChange();
115 }
116
117 /**
118 * Gets the full string that should be send as error report.
119 * @return The string.
120 * @since 10585
121 */
122 public String getReportText() {
123 StringWriter stringWriter = new StringWriter();
124 PrintWriter out = new PrintWriter(stringWriter);
125 if (isIncludeStatusReport()) {
126 try {
127 out.println(ShowStatusReportAction.getReportHeader());
128 } catch (RuntimeException e) { // NOPMD
129 out.println("Could not generate status report: " + e.getMessage());
130 }
131 }
132 if (isIncludeData()) {
133 exception.printReportDataTo(out);
134 }
135 exception.printReportStackTo(out);
136 if (isIncludeAllStackTraces()) {
137 exception.printReportThreadsTo(out);
138 }
139 return stringWriter.toString().replaceAll("\r", "");
140 }
141
142 /**
143 * Add a new change listener.
144 * @param listener The listener
145 * @since 10585
146 */
147 public void addChangeListener(BugReportListener listener) {
148 listeners.add(listener);
149 }
150
151 /**
152 * Remove a change listener.
153 * @param listener The listener
154 * @since 10585
155 */
156 public void removeChangeListener(BugReportListener listener) {
157 listeners.remove(listener);
158 }
159
160 private void fireChange() {
161 listeners.stream().forEach(l -> l.bugReportChanged(this));
162 }
163
164 /**
165 * This should be called whenever you want to add more information to a given exception.
166 * @param t The throwable that was thrown.
167 * @return A {@link ReportedException} to which you can add additional information.
168 */
169 public static ReportedException intercept(Throwable t) {
170 ReportedException e;
171 if (t instanceof ReportedException) {
172 e = (ReportedException) t;
173 } else {
174 e = new ReportedException(t);
175 }
176 e.startSection(getCallingMethod(2));
177 return e;
178 }
179
180 /**
181 * Find the method that called us.
182 *
183 * @param offset
184 * How many methods to look back in the stack trace. 1 gives the method calling this method, 0 gives you getCallingMethod().
185 * @return The method name.
186 */
187 public static String getCallingMethod(int offset) {
188 StackTraceElement found = getCallingMethod(offset + 1, BugReport.class.getName(), "getCallingMethod"::equals);
189 if (found != null) {
190 return found.getClassName().replaceFirst(".*\\.", "") + '#' + found.getMethodName();
191 } else {
192 return "?";
193 }
194 }
195
196 /**
197 * Find the method that called the given method on the current stack trace.
198 * @param offset
199 * How many methods to look back in the stack trace.
200 * 1 gives the method calling this method, 0 gives you the method with the given name..
201 * @param className The name of the class to search for
202 * @param methodName The name of the method to search for
203 * @return The class and method name or null if it is unknown.
204 */
205 public static StackTraceElement getCallingMethod(int offset, String className, Predicate<String> methodName) {
206 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
207 for (int i = 0; i < stackTrace.length - offset; i++) {
208 StackTraceElement element = stackTrace[i];
209 if (className.equals(element.getClassName()) && methodName.test(element.getMethodName())) {
210 return stackTrace[i + offset];
211 }
212 }
213 return null;
214 }
215
216 /**
217 * A listener that listens to changes to this report.
218 * @author Michael Zangl
219 * @since 10585
220 */
221 @FunctionalInterface
222 public interface BugReportListener {
223 /**
224 * Called whenever this bug report was changed, e.g. the data to be included in it.
225 * @param report The report that was changed.
226 */
227 void bugReportChanged(BugReport report);
228 }
229}
Note: See TracBrowser for help on using the repository browser.