source: josm/trunk/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java

Last change on this file was 19536, checked in by stoecker, 7 weeks ago

remove PMD ImplicitFunctionalInterface, see #24635

  • Property svn:eol-style set to native
File size: 5.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools.bugreport;
3
4import java.util.ArrayList;
5import java.util.LinkedList;
6import java.util.Objects;
7import java.util.concurrent.CopyOnWriteArrayList;
8import java.util.function.Predicate;
9
10import org.openstreetmap.josm.tools.Logging;
11
12/**
13 * This class handles the display of the bug report dialog.
14 * @author Michael Zangl
15 * @since 10819
16 */
17public class BugReportQueue {
18
19 /**
20 * The fallback bug report handler if none is set. Prints the stacktrace on standard error stream.
21 * @since 12770
22 */
23 public static final BugReportHandler FALLBACK_BUGREPORT_HANDLER = (e, index) -> {
24 e.printStackTrace();
25 return BugReportQueue.SuppressionMode.NONE;
26 };
27
28 private static final BugReportQueue INSTANCE = new BugReportQueue();
29
30 private final LinkedList<ReportedException> reportsToDisplay = new LinkedList<>();
31 private boolean suppressAllMessages;
32 private final ArrayList<ReportedException> suppressFor = new ArrayList<>();
33 private Thread displayThread;
34 private BugReportHandler bugReportHandler = FALLBACK_BUGREPORT_HANDLER;
35 private final CopyOnWriteArrayList<Predicate<ReportedException>> handlers = new CopyOnWriteArrayList<>();
36 private int displayedErrors;
37
38 private boolean inReportDialog;
39
40 /**
41 * Class that handles reporting a bug to the user.
42 */
43 @FunctionalInterface
44 public interface BugReportHandler {
45 /**
46 * Handle the bug report for a given exception
47 * @param e The exception to display
48 * @param exceptionCounter A counter of how many exceptions have already been worked on
49 * @return The new suppression status
50 */
51 SuppressionMode handle(ReportedException e, int exceptionCounter);
52 }
53
54 /**
55 * The suppression mode that should be used after the dialog was closed.
56 */
57 public enum SuppressionMode {
58 /**
59 * Suppress no dialogs.
60 */
61 NONE,
62 /**
63 * Suppress only the ones that are for the same error
64 */
65 SAME,
66 /**
67 * Suppress all report dialogs
68 */
69 ALL
70 }
71
72 /**
73 * Submit a new error to be displayed
74 * @param report The error to display
75 */
76 public synchronized void submit(ReportedException report) {
77 Logging.logWithStackTrace(Logging.LEVEL_ERROR, "Handled by bug report queue", report.getCause());
78 if (suppressAllMessages || suppressFor.stream().anyMatch(report::isSame)) {
79 Logging.info("User requested to skip error " + report);
80 } else if (reportsToDisplay.size() > 100 || reportsToDisplay.stream().filter(report::isSame).count() >= 10) {
81 Logging.warn("Too many errors. Dropping " + report);
82 } else {
83 reportsToDisplay.add(report);
84 if (displayThread == null) {
85 displayThread = new Thread(new BugReportDisplayRunnable(), "bug-report-display");
86 displayThread.start();
87 }
88 notifyAll();
89 }
90 }
91
92 private final class BugReportDisplayRunnable implements Runnable {
93
94 private volatile boolean running = true;
95
96 @Override
97 public void run() {
98 try {
99 while (running) {
100 ReportedException e = getNext();
101 handleDialogResult(e, displayFor(e));
102 }
103 } catch (InterruptedException e) {
104 displayFor(BugReport.intercept(e));
105 Thread.currentThread().interrupt();
106 }
107 }
108 }
109
110 private synchronized void handleDialogResult(ReportedException e, SuppressionMode suppress) {
111 if (suppress == SuppressionMode.ALL) {
112 suppressAllMessages = true;
113 reportsToDisplay.clear();
114 } else if (suppress == SuppressionMode.SAME) {
115 suppressFor.add(e);
116 reportsToDisplay.removeIf(e::isSame);
117 }
118 displayedErrors++;
119 inReportDialog = false;
120 }
121
122 private synchronized ReportedException getNext() throws InterruptedException {
123 while (reportsToDisplay.isEmpty()) {
124 wait();
125 }
126 inReportDialog = true;
127 return reportsToDisplay.removeFirst();
128 }
129
130 private SuppressionMode displayFor(ReportedException e) {
131 if (handlers.stream().anyMatch(p -> p.test(e))) {
132 Logging.trace("Intercepted by handler.");
133 return SuppressionMode.NONE;
134 }
135 return bugReportHandler.handle(e, getDisplayedErrors());
136 }
137
138 private synchronized int getDisplayedErrors() {
139 return displayedErrors;
140 }
141
142 /**
143 * Check if the dialog is shown. Should only be used for e.g. debugging.
144 * @return <code>true</code> if the exception handler is still showing the exception to the user.
145 */
146 public synchronized boolean exceptionHandlingInProgress() {
147 return !reportsToDisplay.isEmpty() || inReportDialog;
148 }
149
150 /**
151 * Sets the {@link BugReportHandler} for this queue.
152 * @param bugReportHandler the handler in charge of displaying the bug report. Must not be null
153 * @since 12770
154 */
155 public void setBugReportHandler(BugReportHandler bugReportHandler) {
156 this.bugReportHandler = Objects.requireNonNull(bugReportHandler, "bugReportHandler");
157 }
158
159 /**
160 * Allows you to peek or even intercept the bug reports.
161 * @param handler The handler. It can return false to stop all further handling of the exception.
162 * @since 10886
163 */
164 public void addBugReportHandler(Predicate<ReportedException> handler) {
165 handlers.add(handler);
166 }
167
168 /**
169 * Gets the global bug report queue
170 * @return The queue
171 * @since 10886
172 */
173 public static BugReportQueue getInstance() {
174 return INSTANCE;
175 }
176}
Note: See TracBrowser for help on using the repository browser.