001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools.bugreport; 003 004import java.awt.GraphicsEnvironment; 005import java.util.ArrayList; 006import java.util.LinkedList; 007import java.util.concurrent.CopyOnWriteArrayList; 008import java.util.function.BiFunction; 009import java.util.function.Predicate; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.tools.Logging; 013 014/** 015 * This class handles the display of the bug report dialog. 016 * @author Michael Zangl 017 * @since 10819 018 */ 019public class BugReportQueue { 020 021 private static final BugReportQueue INSTANCE = new BugReportQueue(); 022 023 private final LinkedList<ReportedException> reportsToDisplay = new LinkedList<>(); 024 private boolean suppressAllMessages; 025 private final ArrayList<ReportedException> suppressFor = new ArrayList<>(); 026 private Thread displayThread; 027 private final BiFunction<ReportedException, Integer, SuppressionMode> bugReportHandler = getBestHandler(); 028 private final CopyOnWriteArrayList<Predicate<ReportedException>> handlers = new CopyOnWriteArrayList<>(); 029 private int displayedErrors; 030 031 private boolean inReportDialog; 032 033 /** 034 * The suppression mode that should be used after the dialog was closed. 035 */ 036 public enum SuppressionMode { 037 /** 038 * Suppress no dialogs. 039 */ 040 NONE, 041 /** 042 * Suppress only the ones that are for the same error 043 */ 044 SAME, 045 /** 046 * Suppress all report dialogs 047 */ 048 ALL 049 } 050 051 /** 052 * Submit a new error to be displayed 053 * @param report The error to display 054 */ 055 public synchronized void submit(ReportedException report) { 056 Logging.logWithStackTrace(Logging.LEVEL_ERROR, "Handled by bug report queue", report.getCause()); 057 if (suppressAllMessages || suppressFor.stream().anyMatch(report::isSame)) { 058 Main.info("User requested to skip error " + report); 059 } else if (reportsToDisplay.size() > 100 || reportsToDisplay.stream().filter(report::isSame).count() >= 10) { 060 Main.warn("Too many errors. Dropping " + report); 061 } else { 062 reportsToDisplay.add(report); 063 if (displayThread == null) { 064 displayThread = new Thread(new BugReportDisplayRunnable(), "bug-report-display"); 065 displayThread.start(); 066 } 067 notifyAll(); 068 } 069 } 070 071 private class BugReportDisplayRunnable implements Runnable { 072 073 private volatile boolean running = true; 074 075 @Override 076 public void run() { 077 try { 078 while (running) { 079 ReportedException e = getNext(); 080 handleDialogResult(e, displayFor(e)); 081 } 082 } catch (InterruptedException e) { 083 displayFor(BugReport.intercept(e)); 084 } 085 } 086 } 087 088 private synchronized void handleDialogResult(ReportedException e, SuppressionMode suppress) { 089 if (suppress == SuppressionMode.ALL) { 090 suppressAllMessages = true; 091 reportsToDisplay.clear(); 092 } else if (suppress == SuppressionMode.SAME) { 093 suppressFor.add(e); 094 reportsToDisplay.removeIf(e::isSame); 095 } 096 displayedErrors++; 097 inReportDialog = false; 098 } 099 100 private synchronized ReportedException getNext() throws InterruptedException { 101 while (reportsToDisplay.isEmpty()) { 102 wait(); 103 } 104 inReportDialog = true; 105 return reportsToDisplay.removeFirst(); 106 } 107 108 private SuppressionMode displayFor(ReportedException e) { 109 if (handlers.stream().anyMatch(p -> p.test(e))) { 110 Main.trace("Intercepted by handler."); 111 return SuppressionMode.NONE; 112 } 113 return bugReportHandler.apply(e, getDisplayedErrors()); 114 } 115 116 private synchronized int getDisplayedErrors() { 117 return displayedErrors; 118 } 119 120 /** 121 * Check if the dialog is shown. Should only be used for e.g. debugging. 122 * @return <code>true</code> if the exception handler is still showing the exception to the user. 123 */ 124 public synchronized boolean exceptionHandlingInProgress() { 125 return !reportsToDisplay.isEmpty() || inReportDialog; 126 } 127 128 private static BiFunction<ReportedException, Integer, SuppressionMode> getBestHandler() { 129 if (GraphicsEnvironment.isHeadless()) { 130 return (e, index) -> { 131 e.printStackTrace(); 132 return SuppressionMode.NONE; 133 }; 134 } else { 135 return BugReportDialog::showFor; 136 } 137 } 138 139 /** 140 * Allows you to peek or even intersect the bug reports. 141 * @param handler The handler. It can return false to stop all further handling of the exception. 142 * @since 10886 143 */ 144 public void addBugReportHandler(Predicate<ReportedException> handler) { 145 handlers.add(handler); 146 } 147 148 /** 149 * Gets the global bug report queue 150 * @return The queue 151 * @since 10886 152 */ 153 public static BugReportQueue getInstance() { 154 return INSTANCE; 155 } 156}