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}