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.event.ActionEvent;
008import java.awt.event.KeyEvent;
009import java.io.File;
010import java.io.IOException;
011import java.lang.management.ManagementFactory;
012import java.util.ArrayList;
013import java.util.Arrays;
014import java.util.Collections;
015import java.util.List;
016
017import org.openstreetmap.josm.Main;
018import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
019import org.openstreetmap.josm.tools.ImageProvider;
020import org.openstreetmap.josm.tools.Shortcut;
021
022/**
023 * Restarts JOSM as it was launched. Comes from "restart" plugin, originally written by Upliner.
024 * <br><br>
025 * Mechanisms have been improved based on #8561 discussions and <a href="http://lewisleo.blogspot.jp/2012/08/programmatically-restart-java.html">this article</a>.
026 * @since 5857
027 */
028public class RestartAction extends JosmAction {
029
030    /**
031     * Constructs a new {@code RestartAction}.
032     */
033    public RestartAction() {
034        super(tr("Restart"), "restart", tr("Restart the application."),
035                Shortcut.registerShortcut("file:restart", tr("File: {0}", tr("Restart")), KeyEvent.VK_J, Shortcut.ALT_CTRL_SHIFT), false);
036        putValue("help", ht("/Action/Restart"));
037        putValue("toolbar", "action/restart");
038        Main.toolbar.register(this);
039        setEnabled(isRestartSupported());
040    }
041
042    @Override
043    public void actionPerformed(ActionEvent e) {
044        // If JOSM has been started with property 'josm.restart=true' this means
045        // it is executed by a start script that can handle restart.
046        // Request for restart is indicated by exit code 9.
047        String scriptRestart = System.getProperty("josm.restart");
048        if ("true".equals(scriptRestart)) {
049            Main.exitJosm(true, 9);
050        }
051
052        try {
053            restartJOSM();
054        } catch (IOException ex) {
055            Main.error(ex);
056        }
057    }
058
059    /**
060     * Determines if restarting the application should be possible on this platform.
061     * @return {@code true} if the mandatory system property {@code sun.java.command} is defined, {@code false} otherwise.
062     * @since 5951
063     */
064    public static boolean isRestartSupported() {
065        return System.getProperty("sun.java.command") != null;
066    }
067
068    /**
069     * Restarts the current Java application
070     * @throws IOException
071     */
072    public static void restartJOSM() throws IOException {
073        if (isRestartSupported() && !Main.exitJosm(false, 0)) return;
074        try {
075            // java binary
076            final String java = System.getProperty("java.home") + File.separator + "bin" + File.separator +
077                    (Main.isPlatformWindows() ? "java.exe" : "java");
078            if (!new File(java).isFile()) {
079                throw new IOException("Unable to find suitable java runtime at "+java);
080            }
081            final List<String> cmd = new ArrayList<>(Collections.singleton(java));
082            // vm arguments
083            for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
084                // if it's the agent argument : we ignore it otherwise the
085                // address of the old application and the new one will be in conflict
086                if (!arg.contains("-agentlib")) {
087                    cmd.add(arg);
088                }
089            }
090            // program main and program arguments (be careful a sun property. might not be supported by all JVM)
091            String[] mainCommand = System.getProperty("sun.java.command").split(" ");
092            // look for a .jar in all chunks to support paths with spaces (fix #9077)
093            String jarPath = mainCommand[0];
094            for (int i = 1; i < mainCommand.length && !jarPath.endsWith(".jar"); i++) {
095                jarPath += " " + mainCommand[i];
096            }
097            // program main is a jar
098            if (jarPath.endsWith(".jar")) {
099                // if it's a jar, add -jar mainJar
100                cmd.add("-jar");
101                cmd.add(new File(jarPath).getPath());
102            } else {
103                // else it's a .class, add the classpath and mainClass
104                cmd.add("-cp");
105                cmd.add("\"" + System.getProperty("java.class.path") + "\"");
106                cmd.add(mainCommand[0]);
107            }
108            // if it's webstart add JNLP file
109            String jnlp = System.getProperty("jnlp.application.href");
110            if (jnlp != null) {
111                cmd.add(jnlp);
112            }
113            // finally add program arguments
114            cmd.addAll(Arrays.asList(Main.commandLineArgs));
115            Main.info("Restart "+cmd);
116            // execute the command in a shutdown hook, to be sure that all the
117            // resources have been disposed before restarting the application
118            Runtime.getRuntime().addShutdownHook(new Thread() {
119                @Override
120                public void run() {
121                    try {
122                        Runtime.getRuntime().exec(cmd.toArray(new String[cmd.size()]));
123                    } catch (IOException e) {
124                        Main.error(e);
125                    }
126                }
127            });
128            // exit
129            System.exit(0);
130        } catch (Exception e) {
131            // something went wrong
132            throw new IOException("Error while trying to restart the application", e);
133        }
134    }
135
136    /**
137     * Returns a new {@code ButtonSpec} instance that performs this action.
138     * @return A new {@code ButtonSpec} instance that performs this action.
139     */
140    public static ButtonSpec getRestartButtonSpec() {
141        return new ButtonSpec(
142                tr("Restart"),
143                ImageProvider.get("restart"),
144                tr("Restart the application."),
145                ht("/Action/Restart"),
146                isRestartSupported()
147        );
148    }
149
150    /**
151     * Returns a new {@code ButtonSpec} instance that do not perform this action.
152     * @return A new {@code ButtonSpec} instance that do not perform this action.
153     */
154    public static ButtonSpec getCancelButtonSpec() {
155        return new ButtonSpec(
156                tr("Cancel"),
157                ImageProvider.get("cancel"),
158                tr("Click to restart later."),
159                null /* no specific help context */
160        );
161    }
162
163    /**
164     * Returns default {@code ButtonSpec} instances for this action (Restart/Cancel).
165     * @return Default {@code ButtonSpec} instances for this action.
166     * @see #getRestartButtonSpec
167     * @see #getCancelButtonSpec
168     */
169    public static ButtonSpec[] getButtonSpecs() {
170        return new ButtonSpec[] {
171                getRestartButtonSpec(),
172                getCancelButtonSpec()
173        };
174    }
175}