Ticket #13352: patch-fix-13352.patch

File patch-fix-13352.patch, 22.3 KB (added by michael2402, 8 years ago)
  • src/org/openstreetmap/josm/gui/MapView.java

    diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
    index 9781528..7868015 100644
    a b import org.openstreetmap.josm.tools.AudioPlayer;  
    7575import org.openstreetmap.josm.tools.Shortcut;
    7676import org.openstreetmap.josm.tools.Utils;
    7777import org.openstreetmap.josm.tools.bugreport.BugReport;
    78 import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
    7978
    8079/**
    8180 * This is a component used in the {@link MapFrame} for browsing the map. It use is to
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    824823            painter.paint(paintGraphics);
    825824            g.setPaintMode();
    826825        } catch (RuntimeException t) {
    827             //TODO: only display.
    828             throw BugReport.intercept(t).put("layer", layer).put("bounds", box);
     826            BugReport.intercept(t).put("layer", layer).put("bounds", box).warn();
    829827        }
    830828    }
    831829
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    834832     */
    835833    @Override
    836834    public void paint(Graphics g) {
    837         if (!prepareToDraw()) {
     835        try {
     836            if (!prepareToDraw()) {
     837                return;
     838            }
     839        } catch (RuntimeException e) {
     840            BugReport.intercept(e).put("center", () -> getCenter()).warn();
    838841            return;
    839842        }
    840843
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    912915            paintLayer(visibleLayers.get(i), tempG, box);
    913916        }
    914917
    915         synchronized (temporaryLayers) {
    916             for (MapViewPaintable mvp : temporaryLayers) {
    917                 try {
    918                     mvp.paint(tempG, this, box);
    919                 } catch (RuntimeException e) {
    920                     throw BugReport.intercept(e).put("mvp", mvp);
    921                 }
    922             }
     918        try {
     919            drawTemporaryLayers(tempG, box);
     920        } catch (RuntimeException e) {
     921            BugReport.intercept(e).put("temporaryLayers", temporaryLayers).warn();
    923922        }
    924923
    925924        // draw world borders
    926925        try {
    927926            drawWorldBorders(tempG);
    928927        } catch (RuntimeException e) {
    929             throw BugReport.intercept(e).put("bounds", getProjection()::getWorldBoundsLatLon);
     928            // getProjection() needs to be inside lambda to catch errors.
     929            BugReport.intercept(e).put("bounds", () -> getProjection().getWorldBoundsLatLon()).warn();
    930930        }
    931931
    932932        if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    968968        super.paint(g);
    969969    }
    970970
     971    private void drawTemporaryLayers(Graphics2D tempG, Bounds box) {
     972        synchronized (temporaryLayers) {
     973            for (MapViewPaintable mvp : temporaryLayers) {
     974                try {
     975                    mvp.paint(tempG, this, box);
     976                } catch (RuntimeException e) {
     977                    throw BugReport.intercept(e).put("mvp", mvp);
     978                }
     979            }
     980        }
     981    }
     982
    971983    private void drawWorldBorders(Graphics2D tempG) {
    972984        tempG.setColor(Color.WHITE);
    973985        Bounds b = getProjection().getWorldBoundsLatLon();
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    9941006            zoomTo(initialViewport);
    9951007            initialViewport = null;
    9961008        }
    997         if (BugReportExceptionHandler.exceptionHandlingInProgress())
    998             return false;
    9991009
    10001010        if (getCenter() == null)
    10011011            return false; // no data loaded yet.
  • src/org/openstreetmap/josm/tools/bugreport/BugReportDialog.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportDialog.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportDialog.java
    index 6971922..62d7e79 100644
    a b import javax.swing.JButton;  
    1616import javax.swing.JCheckBox;
    1717import javax.swing.JDialog;
    1818import javax.swing.JLabel;
     19import javax.swing.JOptionPane;
    1920import javax.swing.JPanel;
    2021import javax.swing.UIManager;
    2122
    2223import org.openstreetmap.josm.Main;
    2324import org.openstreetmap.josm.actions.ExpertToggleAction;
     25import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference;
     26import org.openstreetmap.josm.gui.util.GuiHelper;
    2427import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
    2528import org.openstreetmap.josm.gui.widgets.UrlLabel;
     29import org.openstreetmap.josm.plugins.PluginDownloadTask;
     30import org.openstreetmap.josm.plugins.PluginHandler;
    2631import org.openstreetmap.josm.tools.GBC;
    2732import org.openstreetmap.josm.tools.ImageProvider;
    2833import org.openstreetmap.josm.tools.InputMapUtils;
     34import org.openstreetmap.josm.tools.bugreport.BugReportQueue.SuppressionMode;
    2935
    3036/**
    3137 * This is a dialog that can be used to display a bug report.
    public class BugReportDialog extends JDialog {  
    4046    private final JPanel content = new JPanel(new GridBagLayout());
    4147    private final BugReport report;
    4248    private final DebugTextDisplay textPanel;
    43     private JCheckBox cbSuppress;
     49    private JCheckBox cbSuppressSingle;
     50    private JCheckBox cbSuppressAll;
    4451
    4552    /**
    4653     * Create a new dialog.
    public class BugReportDialog extends JDialog {  
    5562        addMessageSection();
    5663
    5764        addUpToDateSection();
    58         // TODO: Notify user about plugin updates
     65        // TODO: Notify user about plugin updates, then remove that notification that is displayed before this dialog is displayed.
    5966
    6067        addCreateTicketSection();
    6168
    public class BugReportDialog extends JDialog {  
    145152
    146153    private void addIgnoreButton() {
    147154        JPanel panel = new JPanel(new GridBagLayout());
    148         cbSuppress = new JCheckBox(tr("Suppress further error dialogs for this session."));
    149         cbSuppress.setVisible(false);
    150         panel.add(cbSuppress, GBC.std().fill(GBC.HORIZONTAL));
     155        cbSuppressSingle = new JCheckBox(tr("Suppress this error for this session."));
     156        cbSuppressSingle.setVisible(false);
     157        panel.add(cbSuppressSingle, GBC.std(0, 0).fill(GBC.HORIZONTAL));
     158        cbSuppressAll = new JCheckBox(tr("Suppress further error dialogs for this session."));
     159        cbSuppressAll.setVisible(false);
     160        panel.add(cbSuppressAll, GBC.std(0, 1).fill(GBC.HORIZONTAL));
    151161        JButton ignore = new JButton(tr("Ignore this error."));
    152162        ignore.addActionListener(e -> closeDialog());
    153         panel.add(ignore, GBC.eol());
     163        panel.add(ignore, GBC.std(1, 0).span(1, 2).anchor(GBC.CENTER));
    154164        content.add(panel, GBC.eol().fill(GBC.HORIZONTAL).insets(20));
    155165    }
    156166
    public class BugReportDialog extends JDialog {  
    159169     * @param showSuppress <code>true</code> to show the suppress errors checkbox.
    160170     */
    161171    public void setShowSuppress(boolean showSuppress) {
    162         cbSuppress.setVisible(showSuppress);
     172        cbSuppressSingle.setVisible(showSuppress);
     173        pack();
     174    }
     175
     176    /**
     177     * Shows or hides the suppress all errors button
     178     * @param showSuppress <code>true</code> to show the suppress errors checkbox.
     179     */
     180    public void setShowSuppressAll(boolean showSuppress) {
     181        cbSuppressAll.setVisible(showSuppress);
    163182        pack();
    164183    }
    165184
    public class BugReportDialog extends JDialog {  
    167186     * Check if the checkbox to suppress further errors was selected
    168187     * @return <code>true</code> if the user wishes to suppress errors.
    169188     */
    170     public boolean shouldSuppressFurtherErrors() {
    171         return cbSuppress.isSelected();
     189    public SuppressionMode shouldSuppressFurtherErrors() {
     190        if (cbSuppressAll.isSelected()) {
     191            return SuppressionMode.ALL;
     192        } else if (cbSuppressSingle.isSelected()) {
     193            return SuppressionMode.SAME;
     194        } else {
     195            return SuppressionMode.NONE;
     196        }
    172197    }
    173198
    174199    private void closeDialog() {
    public class BugReportDialog extends JDialog {  
    198223        }
    199224        return null;
    200225    }
     226
     227    /**
     228     * Show the bug report for a given exception
     229     * @param e The exception to display
     230     * @param exceptionCounter A counter of how many exceptions have already been worked on
     231     * @return The new suppression status
     232     */
     233    public static SuppressionMode showFor(ReportedException e, int exceptionCounter) {
     234        if (e.isOutOfMemory()) {
     235            // do not translate the string, as translation may raise an exception
     236            JOptionPane.showMessageDialog(Main.parent, "JOSM is out of memory. " +
     237                    "Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" +
     238                    "where ### is the number of MB assigned to JOSM (e.g. 256).\n" +
     239                    "Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.",
     240                    "Error",
     241                    JOptionPane.ERROR_MESSAGE
     242                    );
     243            return SuppressionMode.NONE;
     244        } else {
     245            return GuiHelper.runInEDTAndWaitAndReturn(() -> {
     246                PluginDownloadTask downloadTask = PluginHandler.updateOrdisablePluginAfterException(e);
     247                if (downloadTask != null) {
     248                    // Ask for restart to install new plugin
     249                    PluginPreference.notifyDownloadResults(
     250                            Main.parent, downloadTask, !downloadTask.getDownloadedPlugins().isEmpty());
     251                    return SuppressionMode.NONE;
     252                }
     253
     254                BugReport report = new BugReport(e);
     255                BugReportDialog dialog = new BugReportDialog(report);
     256                dialog.setShowSuppress(exceptionCounter > 0);
     257                dialog.setShowSuppressAll(exceptionCounter > 1);
     258                dialog.setVisible(true);
     259                return dialog.shouldSuppressFurtherErrors();
     260            });
     261        }
     262    }
    201263}
  • src/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandler.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandler.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandler.java
    index 461b73f..1decdd1 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.tools.bugreport;
    33
    4 import java.awt.GraphicsEnvironment;
    5 
    6 import javax.swing.JOptionPane;
    7 import javax.swing.SwingUtilities;
    8 
    9 import org.openstreetmap.josm.Main;
    10 import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference;
    11 import org.openstreetmap.josm.plugins.PluginDownloadTask;
    12 import org.openstreetmap.josm.plugins.PluginHandler;
    13 
    144/**
    155 * An exception handler that asks the user to send a bug report.
    166 *
    import org.openstreetmap.josm.plugins.PluginHandler;  
    199 */
    2010public final class BugReportExceptionHandler implements Thread.UncaughtExceptionHandler {
    2111
    22     private static boolean handlingInProgress;
    23     private static volatile BugReporterThread bugReporterThread;
    24     private static int exceptionCounter;
    25     private static boolean suppressExceptionDialogs;
    26 
    27     static final class BugReporterThread extends Thread {
    28 
    29         private final class BugReporterWorker implements Runnable {
    30             private final PluginDownloadTask pluginDownloadTask;
    31 
    32             private BugReporterWorker(PluginDownloadTask pluginDownloadTask) {
    33                 this.pluginDownloadTask = pluginDownloadTask;
    34             }
    35 
    36             @Override
    37             public void run() {
    38                 // Then ask for submitting a bug report, for exceptions thrown from a plugin too, unless updated to a new version
    39                 if (pluginDownloadTask == null) {
    40                     askForBugReport(e);
    41                 } else {
    42                     // Ask for restart to install new plugin
    43                     PluginPreference.notifyDownloadResults(
    44                             Main.parent, pluginDownloadTask, !pluginDownloadTask.getDownloadedPlugins().isEmpty());
    45                 }
    46             }
    47         }
    48 
    49         private final Throwable e;
    50 
    51         /**
    52          * Constructs a new {@code BugReporterThread}.
    53          * @param t the exception
    54          */
    55         private BugReporterThread(Throwable t) {
    56             super("Bug Reporter");
    57             this.e = t;
    58         }
    59 
    60         static void askForBugReport(final Throwable e) {
    61             if (GraphicsEnvironment.isHeadless()) {
    62                 return;
    63             }
    64             BugReport report = new BugReport(BugReport.intercept(e));
    65             BugReportDialog dialog = new BugReportDialog(report);
    66             dialog.setShowSuppress(exceptionCounter > 1);
    67             dialog.setVisible(true);
    68             suppressExceptionDialogs = dialog.shouldSuppressFurtherErrors();
    69         }
    70 
    71         @Override
    72         public void run() {
    73             // Give the user a chance to deactivate the plugin which threw the exception (if it was thrown from a plugin)
    74             SwingUtilities.invokeLater(new BugReporterWorker(PluginHandler.updateOrdisablePluginAfterException(e)));
    75         }
    76     }
    77 
    7812    @Override
    7913    public void uncaughtException(Thread t, Throwable e) {
    8014        handleException(e);
    public final class BugReportExceptionHandler implements Thread.UncaughtException  
    8519     * @param e the exception
    8620     */
    8721    public static synchronized void handleException(final Throwable e) {
    88         if (handlingInProgress || suppressExceptionDialogs)
    89             return;                  // we do not handle secondary exceptions, this gets too messy
    90         if (bugReporterThread != null && bugReporterThread.isAlive())
    91             return;
    92         handlingInProgress = true;
    93         exceptionCounter++;
    94         try {
    95             Main.error(e);
    96             if (Main.parent != null) {
    97                 if (e instanceof OutOfMemoryError) {
    98                     // do not translate the string, as translation may raise an exception
    99                     JOptionPane.showMessageDialog(Main.parent, "JOSM is out of memory. " +
    100                             "Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" +
    101                             "where ### is the number of MB assigned to JOSM (e.g. 256).\n" +
    102                             "Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.",
    103                             "Error",
    104                             JOptionPane.ERROR_MESSAGE
    105                             );
    106                     return;
    107                 }
    108 
    109                 bugReporterThread = new BugReporterThread(e);
    110                 bugReporterThread.start();
    111             }
    112         } finally {
    113             handlingInProgress = false;
    114         }
     22        BugReport.intercept(e).warn();
    11523    }
    11624
    11725    /**
    public final class BugReportExceptionHandler implements Thread.UncaughtException  
    11927     * @return {@code true} if an exception is currently being handled, {@code false} otherwise
    12028     */
    12129    public static boolean exceptionHandlingInProgress() {
    122         return handlingInProgress;
     30        return BugReportQueue.getInstance().exceptionHandlingInProgress();
    12331    }
    12432}
  • new file src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java
    new file mode 100644
    index 0000000..69133c6
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools.bugreport;
     3
     4import java.awt.GraphicsEnvironment;
     5import java.util.ArrayList;
     6import java.util.LinkedList;
     7import java.util.function.BiFunction;
     8
     9import org.openstreetmap.josm.Main;
     10
     11/**
     12 * This class handles the display of the bug report dialog.
     13 * @author Michael Zangl
     14 * @since xxx
     15 */
     16public class BugReportQueue {
     17
     18    private static final BugReportQueue INSTANCE = new BugReportQueue();
     19
     20    private LinkedList<ReportedException> reportsToDisplay = new LinkedList<>();
     21    private boolean suppressAllMessages;
     22    private ArrayList<ReportedException> suppressFor = new ArrayList<>();
     23    private Thread displayThread;
     24    private final BiFunction<ReportedException, Integer, SuppressionMode> bugReportHandler = getBestHandler();
     25    private int displayedErrors;
     26
     27    private boolean inReportDialog;
     28
     29
     30    /**
     31     * The suppression mode that should be used after the dialog was closed.
     32     */
     33    public enum SuppressionMode {
     34        /**
     35         * Suppress no dialogs.
     36         */
     37        NONE,
     38        /**
     39         * Suppress only the ones that are for the same error
     40         */
     41        SAME,
     42        /**
     43         * Suppress all report dialogs
     44         */
     45        ALL
     46    }
     47
     48    /**
     49     * Submit a new error to be displayed
     50     * @param report The error to display
     51     */
     52    public synchronized void submit(ReportedException report) {
     53        if (suppressAllMessages || suppressFor.stream().anyMatch(report::isSame)) {
     54            Main.info("User requested to skip error " + report);
     55        } else if (reportsToDisplay.size() > 100 || reportsToDisplay.stream().filter(report::isSame).count() >= 10) {
     56            Main.warn("Too many errors. Dropping " + report);
     57        } else {
     58            reportsToDisplay.add(report);
     59            if (displayThread == null) {
     60                displayThread = new Thread(this::displayAll, "bug-report-display");
     61                displayThread.start();
     62            }
     63            notifyAll();
     64        }
     65    }
     66
     67    private void displayAll() {
     68        try {
     69            while (true) {
     70                ReportedException e = getNext();
     71                SuppressionMode suppress = displayFor(e);
     72                handleDialogResult(e, suppress);
     73            }
     74        } catch (InterruptedException e) {
     75            displayFor(BugReport.intercept(e));
     76        }
     77    }
     78
     79    private synchronized void handleDialogResult(ReportedException e, SuppressionMode suppress) {
     80        if (suppress == SuppressionMode.ALL) {
     81            suppressAllMessages = true;
     82            reportsToDisplay.clear();
     83        } else if (suppress == SuppressionMode.SAME) {
     84            suppressFor.add(e);
     85            reportsToDisplay.removeIf(e::isSame);
     86        }
     87        displayedErrors++;
     88        inReportDialog = false;
     89    }
     90
     91    private synchronized ReportedException getNext() throws InterruptedException {
     92        while (reportsToDisplay.isEmpty()) {
     93            wait();
     94        }
     95        inReportDialog = true;
     96        return reportsToDisplay.removeFirst();
     97    }
     98
     99    private SuppressionMode displayFor(ReportedException e) {
     100        return bugReportHandler.apply(e, getDisplayedErrors());
     101    }
     102
     103    private synchronized int getDisplayedErrors() {
     104        return displayedErrors;
     105    }
     106
     107    /**
     108     * Check if the dialog is shown. Should only be used for e.g. debugging.
     109     * @return <code>true</code> if the exception handler is still showing the exception to the user.
     110     */
     111    public synchronized boolean exceptionHandlingInProgress() {
     112        return !reportsToDisplay.isEmpty() || inReportDialog;
     113    }
     114
     115    private static BiFunction<ReportedException, Integer, SuppressionMode> getBestHandler() {
     116        if (GraphicsEnvironment.isHeadless()) {
     117            return (e, index) -> { e.printStackTrace(); return SuppressionMode.NONE; };
     118        } else {
     119            return BugReportDialog::showFor;
     120        }
     121    }
     122
     123    public static BugReportQueue getInstance() {
     124        return INSTANCE;
     125    }
     126}
  • src/org/openstreetmap/josm/tools/bugreport/ReportedException.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java b/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java
    index 22efa08..f7c4d36 100644
    a b public class ReportedException extends RuntimeException {  
    6565     */
    6666    public void warn() {
    6767        methodWarningFrom = BugReport.getCallingMethod(2);
    68         // TODO: Open the dialog.
     68        try {
     69            BugReportQueue.getInstance().submit(this);
     70        } catch (RuntimeException e) {
     71            try {
     72                e.printStackTrace();
     73            } catch (RuntimeException e2) {
     74                // we cannot do anything more...
     75                // re-throwing this causes an infinite loop.
     76            }
     77        }
    6978    }
    7079
    7180    /**
    public class ReportedException extends RuntimeException {  
    242251
    243252    @Override
    244253    public String toString() {
    245         return new StringBuilder(48)
    246             .append("CrashReportedException [on thread ")
    247             .append(caughtOnThread)
    248             .append(']')
    249             .toString();
     254        return "ReportedException [thread=" + caughtOnThread + ", exception=" + exception
     255                + ", methodWarningFrom=" + methodWarningFrom + "]";
    250256    }
    251257
    252 
    253258    /**
    254259     * Check if this exception may be caused by a threading issue.
    255260     * @return <code>true</code> if it is.
    public class ReportedException extends RuntimeException {  
    261266    }
    262267
    263268    /**
     269     * Check if this is caused by an out of memory situaition
     270     * @return <code>true</code> if it is.
     271     */
     272    public boolean isOutOfMemory() {
     273        return StreamUtils.toStream(CauseTraceIterator::new).anyMatch(t -> t instanceof OutOfMemoryError);
     274    }
     275
     276    /**
    264277     * Iterates over the causes for this exception. Ignores cycles and aborts iteration then.
    265278     * @author Michal Zangl
    266279     * @since 10585
  • test/unit/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandlerTest.java

    diff --git a/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandlerTest.java b/test/unit/org/openstreetmap/josm/tools/bugreport/BugReportExceptionHandlerTest.java
    index f479d0b..a030600 100644
    a b public class BugReportExceptionHandlerTest {  
    2121    }
    2222
    2323    /**
    24      * Unit test for {@link BugReportExceptionHandler.BugReporterThread#askForBugReport} method.
    25      */
    26     @Test
    27     public void testAskForBugReport() {
    28         BugReportExceptionHandler.BugReporterThread.askForBugReport(new Exception("testAskForBugReport"));
    29     }
    30 
    31     /**
    3224     * Unit test for {@link BugReportExceptionHandler#handleException} method.
    3325     */
    3426    @Test