001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Dimension;
008import java.awt.event.ActionEvent;
009import java.awt.event.KeyEvent;
010import java.lang.management.ManagementFactory;
011import java.util.ArrayList;
012import java.util.Arrays;
013import java.util.Collection;
014import java.util.HashSet;
015import java.util.List;
016import java.util.ListIterator;
017import java.util.Map;
018import java.util.Map.Entry;
019import java.util.Set;
020
021import javax.swing.JScrollPane;
022
023import org.openstreetmap.josm.Main;
024import org.openstreetmap.josm.data.Preferences.Setting;
025import org.openstreetmap.josm.data.Version;
026import org.openstreetmap.josm.data.osm.DataSet;
027import org.openstreetmap.josm.data.osm.DatasetConsistencyTest;
028import org.openstreetmap.josm.gui.ExtendedDialog;
029import org.openstreetmap.josm.gui.widgets.JosmTextArea;
030import org.openstreetmap.josm.plugins.PluginHandler;
031import org.openstreetmap.josm.tools.BugReportExceptionHandler;
032import org.openstreetmap.josm.tools.OpenBrowser;
033import org.openstreetmap.josm.tools.PlatformHookUnixoid;
034import org.openstreetmap.josm.tools.Shortcut;
035import org.openstreetmap.josm.tools.Utils;
036
037/**
038 * @author xeen
039 *
040 * Opens a dialog with useful status information like version numbers for Java, JOSM and plugins
041 * Also includes preferences with stripped username and password
042 */
043public final class ShowStatusReportAction extends JosmAction {
044
045    /**
046     * Constructs a new {@code ShowStatusReportAction}
047     */
048    public ShowStatusReportAction() {
049        super(
050                tr("Show Status Report"),
051                "clock",
052                tr("Show status report with useful information that can be attached to bugs"),
053                Shortcut.registerShortcut("help:showstatusreport", tr("Help: {0}",
054                        tr("Show Status Report")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE), false);
055
056        putValue("help", ht("/Action/ShowStatusReport"));
057        putValue("toolbar", "help/showstatusreport");
058        Main.toolbar.register(this);
059    }
060
061    private static void shortenParam(ListIterator<String> it, String[] param, String source, String target) {
062        if (source != null && target.length() < source.length() && param[1].startsWith(source)) {
063            it.set(param[0] + "=" + param[1].replace(source, target));
064        }
065    }
066
067    /**
068     * Replies the report header (software and system info)
069     * @return The report header (software and system info)
070     */
071    public static String getReportHeader() {
072        StringBuilder text = new StringBuilder();
073        text.append(Version.getInstance().getReleaseAttributes());
074        text.append("\n");
075        text.append("Identification: " + Version.getInstance().getAgentString());
076        text.append("\n");
077        text.append("Memory Usage: ");
078        text.append(Runtime.getRuntime().totalMemory()/1024/1024);
079        text.append(" MB / ");
080        text.append(Runtime.getRuntime().maxMemory()/1024/1024);
081        text.append(" MB (");
082        text.append(Runtime.getRuntime().freeMemory()/1024/1024);
083        text.append(" MB allocated, but free)");
084        text.append("\n");
085        text.append("Java version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + ", " + System.getProperty("java.vm.name"));
086        text.append("\n");
087        if (Main.platform.getClass() == PlatformHookUnixoid.class) {
088            // Add Java package details
089            String packageDetails = ((PlatformHookUnixoid) Main.platform).getJavaPackageDetails();
090            if (packageDetails != null) {
091                text.append("Java package: ");
092                text.append(packageDetails);
093                text.append("\n");
094            }
095            // Add WebStart package details if run from JNLP
096            if (Package.getPackage("javax.jnlp") != null) {
097                String webStartDetails = ((PlatformHookUnixoid) Main.platform).getWebStartPackageDetails();
098                if (webStartDetails != null) {
099                    text.append("WebStart package: ");
100                    text.append(webStartDetails);
101                    text.append("\n");
102                }
103            }
104        }
105        try {
106            final String envJavaHome = System.getenv("JAVA_HOME");
107            final String envJavaHomeAlt = Main.isPlatformWindows() ? "%JAVA_HOME%" : "${JAVA_HOME}";
108            final String propJavaHome = System.getProperty("java.home");
109            final String propJavaHomeAlt = "<java.home>";
110            // Build a new list of VM parameters to modify it below if needed (default implementation returns an UnmodifiableList instance)
111            List<String> vmArguments = new ArrayList<>(ManagementFactory.getRuntimeMXBean().getInputArguments());
112            for (ListIterator<String> it = vmArguments.listIterator(); it.hasNext(); ) {
113                String value = it.next();
114                if (value.contains("=")) {
115                    String[] param = value.split("=");
116                    // Hide some parameters for privacy concerns
117                    if (param[0].toLowerCase().startsWith("-dproxy")) {
118                        it.set(param[0]+"=xxx");
119                    // Shorten some parameters for readability concerns
120                    } else {
121                        shortenParam(it, param, envJavaHome, envJavaHomeAlt);
122                        shortenParam(it, param, propJavaHome, propJavaHomeAlt);
123                    }
124                } else if (value.startsWith("-X")) {
125                    // Remove arguments like -Xbootclasspath/a, -Xverify:remote, that can be very long and unhelpful
126                    it.remove();
127                }
128            }
129            if (!vmArguments.isEmpty()) {
130                text.append("VM arguments: "+ vmArguments.toString().replace("\\\\", "\\"));
131                text.append("\n");
132            }
133        } catch (SecurityException e) {
134            // Ignore exception
135        }
136        if (Main.commandLineArgs.length > 0) {
137            text.append("Program arguments: "+ Arrays.toString(Main.commandLineArgs));
138            text.append("\n");
139        }
140        if (Main.main != null) {
141            DataSet dataset = Main.main.getCurrentDataSet();
142            if (dataset != null) {
143                String result = DatasetConsistencyTest.runTests(dataset);
144                if (result.length() == 0) {
145                    text.append("Dataset consistency test: No problems found\n");
146                } else {
147                    text.append("\nDataset consistency test:\n"+result+"\n");
148                }
149            }
150        }
151        text.append("\n");
152        text.append(PluginHandler.getBugReportText());
153        text.append("\n");
154
155        Collection<String> errorsWarnings = Main.getLastErrorAndWarnings();
156        if (!errorsWarnings.isEmpty()) {
157            text.append("Last errors/warnings:\n");
158            for (String s : errorsWarnings) {
159                text.append("- ").append(s).append("\n");
160            }
161            text.append("\n");
162        }
163
164        return text.toString();
165    }
166
167    @Override
168    public void actionPerformed(ActionEvent e) {
169        StringBuilder text = new StringBuilder();
170        String reportHeader = getReportHeader();
171        text.append(reportHeader);
172        try {
173            Map<String, Setting<?>> settings = Main.pref.getAllSettings();
174            Set<String> keys = new HashSet<>(settings.keySet());
175            for (String key : keys) {
176                // Remove sensitive information from status report
177                if (key.startsWith("marker.show") || key.contains("username") || key.contains("password") || key.contains("access-token")) {
178                    settings.remove(key);
179                }
180            }
181            for (Entry<String, Setting<?>> entry : settings.entrySet()) {
182                text.append(entry.getKey()).append("=").append(entry.getValue().getValue().toString()).append("\n");
183            }
184        } catch (Exception x) {
185            Main.error(x);
186        }
187
188        JosmTextArea ta = new JosmTextArea(text.toString());
189        ta.setWrapStyleWord(true);
190        ta.setLineWrap(true);
191        ta.setEditable(false);
192        JScrollPane sp = new JScrollPane(ta);
193
194        ExtendedDialog ed = new ExtendedDialog(Main.parent,
195                tr("Status Report"),
196                new String[] {tr("Copy to clipboard and close"), tr("Report bug"), tr("Close") });
197        ed.setButtonIcons(new String[] {"copy.png", "bug.png", "cancel.png" });
198        ed.setContent(sp, false);
199        ed.setMinimumSize(new Dimension(380, 200));
200        ed.setPreferredSize(new Dimension(700, Main.parent.getHeight()-50));
201
202        switch (ed.showDialog().getValue()) {
203            case 1: Utils.copyToClipboard(text.toString()); break;
204            case 2: OpenBrowser.displayUrl(BugReportExceptionHandler.getBugReportUrl(
205                        Utils.strip(reportHeader)).toExternalForm()) ; break;
206        }
207    }
208}