Ticket #12905: patch-bug-report-generate-report.patch

File patch-bug-report-generate-report.patch, 20.0 KB (added by michael2402, 9 years ago)
  • new file src/org/openstreetmap/josm/tools/StreamUtils.java

    diff --git a/src/org/openstreetmap/josm/tools/StreamUtils.java b/src/org/openstreetmap/josm/tools/StreamUtils.java
    new file mode 100644
    index 0000000..ead9c1c
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import java.util.Iterator;
     5import java.util.Spliterator;
     6import java.util.Spliterators;
     7import java.util.stream.Stream;
     8import java.util.stream.StreamSupport;
     9
     10/**
     11 * Utility methods for streams.
     12 * @author Michael Zangl
     13 * @since xxx
     14 */
     15public final class StreamUtils {
     16
     17    /**
     18     * Untility class
     19     */
     20    private StreamUtils() {}
     21
     22    /**
     23     * Convert an iterator to a stream.
     24     * @param <T> The element type to iterate over
     25     * @param iterator The iterator
     26     * @return The stream of for that iterator.
     27     */
     28    public static <T> Stream<T> toStream(Iterator<? extends T> iterator) {
     29        Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
     30        return StreamSupport.stream(spliterator, false);
     31    }
     32}
  • src/org/openstreetmap/josm/tools/bugreport/BugReport.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReport.java b/src/org/openstreetmap/josm/tools/bugreport/BugReport.java
    index 16758bf..a2f2287 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.tools.bugreport;
    33
     4import java.io.PrintWriter;
     5import java.io.StringWriter;
     6import java.util.concurrent.CopyOnWriteArrayList;
     7
     8import org.openstreetmap.josm.actions.ShowStatusReportAction;
     9
    410/**
    511 * This class contains utility methods to create and handle a bug report.
    612 * <p>
    package org.openstreetmap.josm.tools.bugreport;  
    1925 * String tag = "...";
    2026 * try {
    2127 *   ... your code ...
    22  * } catch (Throwable t) {
     28 * } catch (RuntimeException t) {
    2329 *   throw BugReport.intercept(t).put("id", id).put("tag", tag);
    2430 * }
    2531 * </pre>
    package org.openstreetmap.josm.tools.bugreport;  
    3137 * @since 10285
    3238 */
    3339public class BugReport {
     40    private boolean includeStatusReport = true;
     41    private boolean includeData = true;
     42    private boolean includeAllStackTraces;
     43    private ReportedException exception;
     44    private final CopyOnWriteArrayList<BugReportListener> listeners = new CopyOnWriteArrayList<>();
     45
    3446    /**
    3547     * Create a new bug report
    3648     * @param e The {@link ReportedException} to use. No more data should be added after creating the report.
    3749     */
    3850    public BugReport(ReportedException e) {
    39         // TODO: Use this class to create the bug report.
     51        this.exception = e;
     52        includeAllStackTraces = e.mayHaveConcurrentSource();
     53    }
     54
     55    /**
     56     * Get if this report should include a system status report
     57     * @return <code>true</code> to include it.
     58     * @since xxx
     59     */
     60    public boolean getIncludeStatusReport() {
     61        return includeStatusReport;
     62    }
     63
     64    /**
     65     * Set if this report should include a system status report
     66     * @param includeStatusReport if the status report should be included
     67     * @since xxx
     68     */
     69    public void setIncludeStatusReport(boolean includeStatusReport) {
     70        this.includeStatusReport = includeStatusReport;
     71        fireChange();
     72    }
     73
     74    /**
     75     * Get if this report should include the data that was traced.
     76     * @return <code>true</code> to include it.
     77     * @since xxx
     78     */
     79    public boolean getIncludeData() {
     80        return includeData;
     81    }
     82
     83    /**
     84     * Set if this report should include the data that was traced.
     85     * @param includeData if data should be included
     86     * @since xxx
     87     */
     88    public void setIncludeData(boolean includeData) {
     89        this.includeData = includeData;
     90        fireChange();
     91    }
     92
     93    /**
     94     * Get if this report should include the stack traces for all other threads.
     95     * @return <code>true</code> to include it.
     96     * @since xxx
     97     */
     98    public boolean getIncludeAllStackTraces() {
     99        return includeAllStackTraces;
     100    }
     101
     102    /**
     103     * Sets if this report should include the stack traces for all other threads.
     104     * @param includeAllStackTraces if all stack traces should be included
     105     * @since xxx
     106     */
     107    public void setIncludeAllStackTraces(boolean includeAllStackTraces) {
     108        this.includeAllStackTraces = includeAllStackTraces;
     109        fireChange();
     110    }
     111
     112    /**
     113     * Gets the full string that should be send as error report.
     114     * @return The string.
     115     * @since xxx
     116     */
     117    public String getReportText() {
     118        StringWriter stringWriter = new StringWriter();
     119        PrintWriter out = new PrintWriter(stringWriter);
     120        if (getIncludeStatusReport()) {
     121            out.println(ShowStatusReportAction.getReportHeader());
     122        }
     123        if (getIncludeData()) {
     124            exception.printReportDataTo(out);
     125        }
     126        exception.printReportStackTo(out);
     127        if (getIncludeAllStackTraces()) {
     128            exception.printReportThreadsTo(out);
     129        }
     130        return stringWriter.toString().replaceAll("\r", "");
     131    }
     132
     133    /**
     134     * Add a new change listener.
     135     * @param listener The listener
     136     * @since xxx
     137     */
     138    public void addChangeListener(BugReportListener listener) {
     139        listeners.add(listener);
     140    }
     141
     142    /**
     143     * Remove a change listener.
     144     * @param listener The listener
     145     * @since xxx
     146     */
     147    public void removeChangeListener(BugReportListener listener) {
     148        listeners.remove(listener);
     149    }
     150
     151    private void fireChange() {
     152        listeners.stream().forEach(l -> l.bugReportChanged(this));
    40153    }
    41154
    42155    /**
    public class BugReport {  
    74187        }
    75188        return "?";
    76189    }
     190
     191    /**
     192     * A listener that listens to changes to this report.
     193     * @author Michael Zangl
     194     * @since xxx
     195     */
     196    @FunctionalInterface
     197    public interface BugReportListener {
     198        /**
     199         * Called whenever this bug report was changed, e.g. the data to be included in it.
     200         * @param report The report that was changed.
     201         */
     202        void bugReportChanged(BugReport report);
     203    }
    77204}
  • 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 054eca0..c31c15c 100644
    a b public final class BugReportExceptionHandler implements Thread.UncaughtException  
    102102                                String.valueOf(josmVersion), String.valueOf(latestVersion));
    103103                    }
    104104                } catch (IOException | NumberFormatException ex) {
    105                     Main.warn("Unable to detect latest version of JOSM: "+ex.getMessage());
     105                    Main.warn("Unable to detect latest version of JOSM: "+ex.getMessage(), ex);
    106106                }
    107107            }
    108108            // Build panel
    public final class BugReportExceptionHandler implements Thread.UncaughtException  
    136136                try {
    137137                    Main.platform.openUrl(Main.getJOSMWebsite());
    138138                } catch (IOException ex) {
    139                     Main.warn("Unable to access JOSM website: "+ex.getMessage());
     139                    Main.warn("Unable to access JOSM website: "+ex.getMessage(), ex);
    140140                }
    141141            } else {
    142142                // "Report bug"
    public final class BugReportExceptionHandler implements Thread.UncaughtException  
    196196    }
    197197
    198198    static JPanel buildPanel(final Throwable e) {
    199         StringWriter stack = new StringWriter();
    200         PrintWriter writer = new PrintWriter(stack);
     199        DebugTextDisplay textarea;
    201200        if (e instanceof ReportedException) {
    202201            // Temporary!
    203             ((ReportedException) e).printReportDataTo(writer);
    204             ((ReportedException) e).printReportStackTo(writer);
     202            textarea = new DebugTextDisplay(new BugReport((ReportedException) e));
    205203        } else {
     204            StringWriter stack = new StringWriter();
     205            PrintWriter writer = new PrintWriter(stack);
    206206            e.printStackTrace(writer);
     207            String text = ShowStatusReportAction.getReportHeader() + stack.getBuffer().toString();
     208            textarea = new DebugTextDisplay(text);
    207209        }
    208210
    209         String text = ShowStatusReportAction.getReportHeader() + stack.getBuffer().toString();
    210         text = text.replaceAll("\r", "");
    211 
    212211        JPanel p = new JPanel(new GridBagLayout());
    213212        p.add(new JMultilineLabel(
    214213                tr("You have encountered an error in JOSM. Before you file a bug report " +
    public final class BugReportExceptionHandler implements Thread.UncaughtException  
    219218                tr("You should also update your plugins. If neither of those help please " +
    220219                        "file a bug report in our bugtracker using this link:")),
    221220                        GBC.eol().fill(GridBagConstraints.HORIZONTAL));
    222         p.add(new JButton(new ReportBugAction(text)), GBC.eop().insets(8, 0, 0, 0));
     221        p.add(new JButton(new ReportBugAction(textarea.getCodeText())), GBC.eop().insets(8, 0, 0, 0));
    223222        p.add(new JMultilineLabel(
    224223                tr("There the error information provided below should already be " +
    225224                        "filled in for you. Please include information on how to reproduce " +
    public final class BugReportExceptionHandler implements Thread.UncaughtException  
    230229                        "below at this URL:")), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
    231230        p.add(new UrlLabel(Main.getJOSMWebsite()+"/newticket", 2), GBC.eop().insets(8, 0, 0, 0));
    232231
    233         // Wiki formatting for manual copy-paste
    234         DebugTextDisplay textarea = new DebugTextDisplay(text);
    235 
    236232        if (textarea.copyToClippboard()) {
    237233            p.add(new JLabel(tr("(The text has already been copied to your clipboard.)")),
    238234                    GBC.eop().fill(GridBagConstraints.HORIZONTAL));
  • new file src/org/openstreetmap/josm/tools/bugreport/BugReportSettingsPanel.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportSettingsPanel.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportSettingsPanel.java
    new file mode 100644
    index 0000000..4881906
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools.bugreport;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import javax.swing.BoxLayout;
     7import javax.swing.JCheckBox;
     8import javax.swing.JPanel;
     9
     10/**
     11 * This panel displays the settings that can be changed before submitting a bug report to the web page.
     12 * @author Michael Zangl
     13 * @since xxx
     14 */
     15public class BugReportSettingsPanel extends JPanel {
     16    /**
     17     * Creates the new settings panel.
     18     * @param report The report this panel should influence.
     19     */
     20    public BugReportSettingsPanel(BugReport report) {
     21        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
     22
     23        JCheckBox statusReport = new JCheckBox(tr("Include the system status report."));
     24        statusReport.setSelected(report.getIncludeStatusReport());
     25        statusReport.addChangeListener(e -> report.setIncludeStatusReport(statusReport.isSelected()));
     26        add(statusReport);
     27
     28        JCheckBox data = new JCheckBox(tr("Include information about the data that was worked on."));
     29        data.setSelected(report.getIncludeData());
     30        data.addChangeListener(e -> report.setIncludeData(data.isSelected()));
     31        add(data);
     32
     33        JCheckBox allStackTraces = new JCheckBox(tr("Include all stack traces."));
     34        allStackTraces.setSelected(report.getIncludeAllStackTraces());
     35        allStackTraces.addChangeListener(e -> report.setIncludeAllStackTraces(allStackTraces.isSelected()));
     36        add(allStackTraces);
     37    }
     38}
  • src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java b/src/org/openstreetmap/josm/tools/bugreport/DebugTextDisplay.java
    index a0e2841..dffe029 100644
    a b import org.openstreetmap.josm.tools.Utils;  
    1414 * @since 10055
    1515 */
    1616public class DebugTextDisplay extends JScrollPane {
     17    private static final String CODE_PATTERN = "{{{%n%s%n}}}";
    1718    private String text;
     19    private JosmTextArea textArea;
    1820
    1921    /**
    20      * Creates a new text are with the fixed text
    21      * @param textToDisplay The text to display.
     22     * Creates a new text area.
     23     * @since xxx
    2224     */
    23     public DebugTextDisplay(String textToDisplay) {
    24         text = "{{{\n" + Utils.strip(textToDisplay) + "\n}}}";
    25         JosmTextArea textArea = new JosmTextArea(text);
     25    private DebugTextDisplay() {
     26        textArea = new JosmTextArea();
    2627        textArea.setCaretPosition(0);
    2728        textArea.setEditable(false);
    2829        setViewportView(textArea);
    public class DebugTextDisplay extends JScrollPane {  
    3031    }
    3132
    3233    /**
    33      * Copies the debug text to the clippboard.
     34     * Creates a new text area with an inital text to display
     35     * @param textToDisplay The text to display.
     36     */
     37    public DebugTextDisplay(String textToDisplay) {
     38        this();
     39        setCodeText(textToDisplay);
     40    }
     41
     42    /**
     43     * Creates a new text area that displays the bug report data
     44     * @param report The bug report data to display.
     45     */
     46    public DebugTextDisplay(BugReport report) {
     47        this();
     48        setCodeText(report.getReportText());
     49        report.addChangeListener(e -> setCodeText(report.getReportText()));
     50    }
     51
     52    /**
     53     * Sets the text that should be displayed in this view.
     54     * @param textToDisplay The text
     55     * @since xxx
     56     */
     57    private void setCodeText(String textToDisplay) {
     58        text = Utils.strip(textToDisplay).replaceAll("\r", "");
     59        textArea.setText(String.format(CODE_PATTERN, text));
     60    }
     61
     62    /**
     63     * Copies the debug text to the clippboard. This includes the code tags for trac.
    3464     * @return <code>true</code> if copy was successful
     65     * @since 10055
    3566     */
    3667    public boolean copyToClippboard() {
    37         return Utils.copyToClipboard(text);
     68        return Utils.copyToClipboard(String.format(CODE_PATTERN, text));
     69    }
     70
     71    /**
     72     * Gets the text this are displays, without the code tag.
     73     * @return The stripped text set by {@link #setCodeText(String)}
     74     * @since xxx
     75     */
     76    public String getCodeText() {
     77        return text;
    3878    }
    3979}
  • 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 41bf1ba..054e666 100644
    a b  
    22package org.openstreetmap.josm.tools.bugreport;
    33
    44import java.io.PrintWriter;
     5import java.io.Serializable;
     6import java.lang.reflect.InvocationTargetException;
    57import java.util.ArrayList;
    68import java.util.Arrays;
    79import java.util.Collection;
    810import java.util.Collections;
     11import java.util.ConcurrentModificationException;
    912import java.util.IdentityHashMap;
     13import java.util.Iterator;
    1014import java.util.LinkedList;
    1115import java.util.Map;
    1216import java.util.Map.Entry;
     17import java.util.NoSuchElementException;
    1318import java.util.Set;
    1419
    1520import org.openstreetmap.josm.Main;
     21import org.openstreetmap.josm.tools.StreamUtils;
    1622
    1723/**
    1824 * This is a special exception that cannot be directly thrown.
    import org.openstreetmap.josm.Main;  
    2430 * @since 10285
    2531 */
    2632public class ReportedException extends RuntimeException {
    27     private static final int MAX_COLLECTION_ENTRIES = 30;
    2833    /**
    29      *
     34     * How many entries of a collection to include in the bug report.
    3035     */
     36    private static final int MAX_COLLECTION_ENTRIES = 30;
     37
    3138    private static final long serialVersionUID = 737333873766201033L;
     39
    3240    /**
    3341     * We capture all stack traces on exception creation. This allows us to trace synchonization problems better. We cannot be really sure what
    3442     * happened but we at least see which threads
    3543     */
    3644    private final transient Map<Thread, StackTraceElement[]> allStackTraces;
    37     private final transient LinkedList<Section> sections = new LinkedList<>();
     45    private final LinkedList<Section> sections = new LinkedList<>();
    3846    private final transient Thread caughtOnThread;
    3947    private final Throwable exception;
    4048    private String methodWarningFrom;
    public class ReportedException extends RuntimeException {  
    143151            return false;
    144152        }
    145153
    146         Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
    147         return hasSameStackTrace(dejaVu, this.exception, e.exception);
     154        return hasSameStackTrace(new CauseTraceIterator(), e.exception);
    148155    }
    149156
    150     private static boolean hasSameStackTrace(Set<Throwable> dejaVu, Throwable e1, Throwable e2) {
    151         if (dejaVu.contains(e1)) {
    152             // cycle. If it was the same until here, we assume both have that cycle.
     157    private static boolean hasSameStackTrace(CauseTraceIterator causeTraceIterator, Throwable e2) {
     158        if (!causeTraceIterator.hasNext()) {
     159            // all done.
    153160            return true;
    154161        }
    155         dejaVu.add(e1);
    156 
     162        Throwable e1 = causeTraceIterator.next();
    157163        StackTraceElement[] t1 = e1.getStackTrace();
    158164        StackTraceElement[] t2 = e2.getStackTrace();
    159165
    public class ReportedException extends RuntimeException {  
    166172        if ((c1 == null) != (c2 == null)) {
    167173            return false;
    168174        } else if (c1 != null) {
    169             return hasSameStackTrace(dejaVu, c1, c2);
     175            return hasSameStackTrace(causeTraceIterator, c2);
    170176        } else {
    171177            return true;
    172178        }
    public class ReportedException extends RuntimeException {  
    227233            .toString();
    228234    }
    229235
    230     private static class SectionEntry {
     236
     237    /**
     238     * Check if this exception may be caused by a threading issue.
     239     * @return <code>true</code> if it is.
     240     * @since xxx
     241     */
     242    public boolean mayHaveConcurrentSource() {
     243        return StreamUtils.toStream(new CauseTraceIterator())
     244                .anyMatch(t -> t instanceof ConcurrentModificationException || t instanceof InvocationTargetException);
     245    }
     246
     247    /**
     248     * Iterates over the causes for this exception. Ignores cycles and aborts iteration then.
     249     * @author Michal Zangl
     250     * @since xxx
     251     */
     252    private final class CauseTraceIterator implements Iterator<Throwable> {
     253        private Throwable current = exception;
     254        private final Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
     255
     256        @Override
     257        public boolean hasNext() {
     258            return current != null;
     259        }
     260
     261        @Override
     262        public Throwable next() {
     263            if (!hasNext()) {
     264                throw new NoSuchElementException();
     265            }
     266            Throwable toReturn = current;
     267            advance();
     268            return toReturn;
     269        }
     270
     271        private void advance() {
     272            dejaVu.add(current);
     273            current = current.getCause();
     274            if (current != null && dejaVu.contains(current)) {
     275                current = null;
     276            }
     277        }
     278    }
     279
     280    private static class SectionEntry implements Serializable {
     281
     282        private static final long serialVersionUID = 1L;
     283
    231284        private final String key;
    232285        private final String value;
    233286
    public class ReportedException extends RuntimeException {  
    248301        }
    249302    }
    250303
    251     private static class Section {
     304    private static class Section implements Serializable {
     305
     306        private static final long serialVersionUID = 1L;
    252307
    253308        private String sectionName;
    254309        private ArrayList<SectionEntry> entries = new ArrayList<>();