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