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