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.Collection; 015import java.util.List; 016 017import org.openstreetmap.josm.Main; 018import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 019import org.openstreetmap.josm.gui.io.SaveLayersDialog; 020import org.openstreetmap.josm.tools.ImageProvider; 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 027 * <a href="http://lewisleo.blogspot.jp/2012/08/programmatically-restart-java.html">this article</a>. 028 * @since 5857 029 */ 030public class RestartAction extends JosmAction { 031 032 // AppleScript to restart OS X package 033 private static final String RESTART_APPLE_SCRIPT = 034 "tell application \"System Events\"\n" 035 + "repeat until not (exists process \"JOSM\")\n" 036 + "delay 0.2\n" 037 + "end repeat\n" 038 + "end tell\n" 039 + "tell application \"JOSM\" to activate"; 040 041 /** 042 * Constructs a new {@code RestartAction}. 043 */ 044 public RestartAction() { 045 super(tr("Restart"), "restart", tr("Restart the application."), 046 Shortcut.registerShortcut("file:restart", tr("File: {0}", tr("Restart")), KeyEvent.VK_J, Shortcut.ALT_CTRL_SHIFT), false); 047 putValue("help", ht("/Action/Restart")); 048 putValue("toolbar", "action/restart"); 049 if (Main.toolbar != null) { 050 Main.toolbar.register(this); 051 } 052 setEnabled(isRestartSupported()); 053 } 054 055 @Override 056 public void actionPerformed(ActionEvent e) { 057 // If JOSM has been started with property 'josm.restart=true' this means 058 // it is executed by a start script that can handle restart. 059 // Request for restart is indicated by exit code 9. 060 String scriptRestart = System.getProperty("josm.restart"); 061 if ("true".equals(scriptRestart)) { 062 Main.exitJosm(true, 9, SaveLayersDialog.Reason.RESTART); 063 } 064 065 try { 066 restartJOSM(); 067 } catch (IOException ex) { 068 Main.error(ex); 069 } 070 } 071 072 /** 073 * Determines if restarting the application should be possible on this platform. 074 * @return {@code true} if the mandatory system property {@code sun.java.command} is defined, {@code false} otherwise. 075 * @since 5951 076 */ 077 public static boolean isRestartSupported() { 078 return System.getProperty("sun.java.command") != null; 079 } 080 081 /** 082 * Restarts the current Java application. 083 * @throws IOException in case of any I/O error 084 */ 085 public static void restartJOSM() throws IOException { 086 if (isRestartSupported() && !Main.exitJosm(false, 0, SaveLayersDialog.Reason.RESTART)) return; 087 final List<String> cmd; 088 // special handling for OSX .app package 089 if (Main.isPlatformOsx() && System.getProperty("java.library.path").contains("/JOSM.app/Contents/MacOS")) { 090 cmd = getAppleCommands(); 091 } else { 092 cmd = getCommands(); 093 } 094 Main.info("Restart "+cmd); 095 if (Main.isDebugEnabled() && Main.pref.getBoolean("restart.debug.simulation")) { 096 Main.debug("Restart cancelled to get debug info"); 097 return; 098 } 099 // execute the command in a shutdown hook, to be sure that all the 100 // resources have been disposed before restarting the application 101 Runtime.getRuntime().addShutdownHook(new Thread("josm-restarter") { 102 @Override 103 public void run() { 104 try { 105 Runtime.getRuntime().exec(cmd.toArray(new String[cmd.size()])); 106 } catch (IOException e) { 107 Main.error(e); 108 } 109 } 110 }); 111 // exit 112 System.exit(0); 113 } 114 115 private static List<String> getAppleCommands() { 116 final List<String> cmd = new ArrayList<>(); 117 cmd.add("/usr/bin/osascript"); 118 for (String line : RESTART_APPLE_SCRIPT.split("\n")) { 119 cmd.add("-e"); 120 cmd.add(line); 121 } 122 return cmd; 123 } 124 125 private static List<String> getCommands() throws IOException { 126 final List<String> cmd = new ArrayList<>(); 127 // java binary 128 cmd.add(getJavaRuntime()); 129 // vm arguments 130 addVMArguments(cmd); 131 // Determine webstart JNLP file. Use jnlpx.origFilenameArg instead of jnlp.application.href, 132 // because only this one is present when run from j2plauncher.exe (see #10795) 133 final String jnlp = System.getProperty("jnlpx.origFilenameArg"); 134 // program main and program arguments (be careful a sun property. might not be supported by all JVM) 135 final String javaCommand = System.getProperty("sun.java.command"); 136 String[] mainCommand = javaCommand.split(" "); 137 if (javaCommand.endsWith(".jnlp") && jnlp == null) { 138 // see #11751 - jnlp on Linux 139 if (Main.isDebugEnabled()) { 140 Main.debug("Detected jnlp without jnlpx.origFilenameArg property set"); 141 } 142 cmd.addAll(Arrays.asList(mainCommand)); 143 } else { 144 // look for a .jar in all chunks to support paths with spaces (fix #9077) 145 StringBuilder sb = new StringBuilder(mainCommand[0]); 146 for (int i = 1; i < mainCommand.length && !mainCommand[i-1].endsWith(".jar"); i++) { 147 sb.append(' ').append(mainCommand[i]); 148 } 149 String jarPath = sb.toString(); 150 // program main is a jar 151 if (jarPath.endsWith(".jar")) { 152 // if it's a jar, add -jar mainJar 153 cmd.add("-jar"); 154 cmd.add(new File(jarPath).getPath()); 155 } else { 156 // else it's a .class, add the classpath and mainClass 157 cmd.add("-cp"); 158 cmd.add('"' + System.getProperty("java.class.path") + '"'); 159 cmd.add(mainCommand[0]); 160 } 161 // add JNLP file. 162 if (jnlp != null) { 163 cmd.add(jnlp); 164 } 165 } 166 // finally add program arguments 167 cmd.addAll(Main.getCommandLineArgs()); 168 return cmd; 169 } 170 171 private static String getJavaRuntime() throws IOException { 172 final String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + 173 (Main.isPlatformWindows() ? "java.exe" : "java"); 174 if (!new File(java).isFile()) { 175 throw new IOException("Unable to find suitable java runtime at "+java); 176 } 177 return java; 178 } 179 180 private static void addVMArguments(Collection<String> cmd) { 181 List<String> arguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); 182 if (Main.isDebugEnabled()) { 183 Main.debug("VM arguments: "+arguments); 184 } 185 for (String arg : arguments) { 186 // When run from jp2launcher.exe, jnlpx.remove is true, while it is not when run from javaws 187 // Always set it to false to avoid error caused by a missing jnlp file on the second restart 188 arg = arg.replace("-Djnlpx.remove=true", "-Djnlpx.remove=false"); 189 // if it's the agent argument : we ignore it otherwise the 190 // address of the old application and the new one will be in conflict 191 if (!arg.contains("-agentlib")) { 192 cmd.add(arg); 193 } 194 } 195 } 196 197 /** 198 * Returns a new {@code ButtonSpec} instance that performs this action. 199 * @return A new {@code ButtonSpec} instance that performs this action. 200 */ 201 public static ButtonSpec getRestartButtonSpec() { 202 return new ButtonSpec( 203 tr("Restart"), 204 ImageProvider.get("restart"), 205 tr("Restart the application."), 206 ht("/Action/Restart"), 207 isRestartSupported() 208 ); 209 } 210 211 /** 212 * Returns a new {@code ButtonSpec} instance that do not perform this action. 213 * @return A new {@code ButtonSpec} instance that do not perform this action. 214 */ 215 public static ButtonSpec getCancelButtonSpec() { 216 return new ButtonSpec( 217 tr("Cancel"), 218 ImageProvider.get("cancel"), 219 tr("Click to restart later."), 220 null /* no specific help context */ 221 ); 222 } 223 224 /** 225 * Returns default {@code ButtonSpec} instances for this action (Restart/Cancel). 226 * @return Default {@code ButtonSpec} instances for this action. 227 * @see #getRestartButtonSpec 228 * @see #getCancelButtonSpec 229 */ 230 public static ButtonSpec[] getButtonSpecs() { 231 return new ButtonSpec[] { 232 getRestartButtonSpec(), 233 getCancelButtonSpec() 234 }; 235 } 236}