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