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