// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools.bugreport; import java.awt.GraphicsEnvironment; import java.util.ArrayList; import java.util.LinkedList; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiFunction; import java.util.function.Predicate; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.tools.Logging; /** * This class handles the display of the bug report dialog. * @author Michael Zangl * @since 10819 */ public class BugReportQueue { private static final BugReportQueue INSTANCE = new BugReportQueue(); private final LinkedList<ReportedException> reportsToDisplay = new LinkedList<>(); private boolean suppressAllMessages; private final ArrayList<ReportedException> suppressFor = new ArrayList<>(); private Thread displayThread; private final BiFunction<ReportedException, Integer, SuppressionMode> bugReportHandler = getBestHandler(); private final CopyOnWriteArrayList<Predicate<ReportedException>> handlers = new CopyOnWriteArrayList<>(); private int displayedErrors; private boolean inReportDialog; /** * The suppression mode that should be used after the dialog was closed. */ public enum SuppressionMode { /** * Suppress no dialogs. */ NONE, /** * Suppress only the ones that are for the same error */ SAME, /** * Suppress all report dialogs */ ALL } /** * Submit a new error to be displayed * @param report The error to display */ public synchronized void submit(ReportedException report) { Logging.logWithStackTrace(Logging.LEVEL_ERROR, "Handled by bug report queue", report.getCause()); if (suppressAllMessages || suppressFor.stream().anyMatch(report::isSame)) { Main.info("User requested to skip error " + report); } else if (reportsToDisplay.size() > 100 || reportsToDisplay.stream().filter(report::isSame).count() >= 10) { Main.warn("Too many errors. Dropping " + report); } else { reportsToDisplay.add(report); if (displayThread == null) { displayThread = new Thread(new BugReportDisplayRunnable(), "bug-report-display"); displayThread.start(); } notifyAll(); } } private class BugReportDisplayRunnable implements Runnable { private volatile boolean running = true; @Override public void run() { try { while (running) { ReportedException e = getNext(); handleDialogResult(e, displayFor(e)); } } catch (InterruptedException e) { displayFor(BugReport.intercept(e)); Thread.currentThread().interrupt(); } } } private synchronized void handleDialogResult(ReportedException e, SuppressionMode suppress) { if (suppress == SuppressionMode.ALL) { suppressAllMessages = true; reportsToDisplay.clear(); } else if (suppress == SuppressionMode.SAME) { suppressFor.add(e); reportsToDisplay.removeIf(e::isSame); } displayedErrors++; inReportDialog = false; } private synchronized ReportedException getNext() throws InterruptedException { while (reportsToDisplay.isEmpty()) { wait(); } inReportDialog = true; return reportsToDisplay.removeFirst(); } private SuppressionMode displayFor(ReportedException e) { if (handlers.stream().anyMatch(p -> p.test(e))) { Main.trace("Intercepted by handler."); return SuppressionMode.NONE; } return bugReportHandler.apply(e, getDisplayedErrors()); } private synchronized int getDisplayedErrors() { return displayedErrors; } /** * Check if the dialog is shown. Should only be used for e.g. debugging. * @return <code>true</code> if the exception handler is still showing the exception to the user. */ public synchronized boolean exceptionHandlingInProgress() { return !reportsToDisplay.isEmpty() || inReportDialog; } private static BiFunction<ReportedException, Integer, SuppressionMode> getBestHandler() { if (GraphicsEnvironment.isHeadless()) { return (e, index) -> { e.printStackTrace(); return SuppressionMode.NONE; }; } else { return BugReportDialog::showFor; } } /** * Allows you to peek or even intersect the bug reports. * @param handler The handler. It can return false to stop all further handling of the exception. * @since 10886 */ public void addBugReportHandler(Predicate<ReportedException> handler) { handlers.add(handler); } /** * Gets the global bug report queue * @return The queue * @since 10886 */ public static BugReportQueue getInstance() { return INSTANCE; } }