source: josm/trunk/src/org/openstreetmap/josm/actions/RestartAction.java

Last change on this file was 18145, checked in by Don-vip, 3 years ago

see #17083 - make restart work with jpackage-based applications

  • Property svn:eol-style set to native
File size: 11.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.io.File;
11import java.io.IOException;
12import java.lang.management.ManagementFactory;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.List;
18
19import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
20import org.openstreetmap.josm.gui.MainApplication;
21import org.openstreetmap.josm.gui.io.SaveLayersDialog;
22import org.openstreetmap.josm.spi.preferences.Config;
23import org.openstreetmap.josm.tools.ImageProvider;
24import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
25import org.openstreetmap.josm.tools.Logging;
26import org.openstreetmap.josm.tools.PlatformManager;
27import org.openstreetmap.josm.tools.Shortcut;
28
29/**
30 * Restarts JOSM as it was launched. Comes from "restart" plugin, originally written by Upliner.
31 * <br><br>
32 * Mechanisms have been improved based on #8561 discussions and
33 * <a href="http://lewisleo.blogspot.jp/2012/08/programmatically-restart-java.html">this article</a>.
34 * @since 5857
35 */
36public class RestartAction extends JosmAction {
37
38 private static final String APPLE_OSASCRIPT = "/usr/bin/osascript";
39 private static final String APPLE_APP_PATH = "/JOSM.app/Contents/";
40
41 // AppleScript to restart OS X package
42 private static final String RESTART_APPLE_SCRIPT =
43 "tell application \"System Events\"\n"
44 + "repeat until not (exists process \"JOSM\")\n"
45 + "delay 0.2\n"
46 + "end repeat\n"
47 + "end tell\n"
48 + "tell application \"JOSM\" to activate";
49
50 // Make sure we're able to retrieve restart commands before initiating shutdown (#13784)
51 private static final List<String> cmd = determineRestartCommands();
52
53 /**
54 * Constructs a new {@code RestartAction}.
55 */
56 public RestartAction() {
57 super(tr("Restart"), "restart", tr("Restart the application."),
58 Shortcut.registerShortcut("file:restart", tr("File: {0}", tr("Restart")), KeyEvent.VK_J, Shortcut.ALT_CTRL_SHIFT), false, false);
59 setHelpId(ht("/Action/Restart"));
60 setToolbarId("action/restart");
61 if (MainApplication.getToolbar() != null) {
62 MainApplication.getToolbar().register(this);
63 }
64 setEnabled(isRestartSupported());
65 }
66
67 @Override
68 public void actionPerformed(ActionEvent e) {
69 restartJOSM();
70 }
71
72 /**
73 * Determines if restarting the application should be possible on this platform.
74 * @return {@code true} if the mandatory system property {@code sun.java.command} is defined, {@code false} otherwise.
75 * @since 5951
76 */
77 public static boolean isRestartSupported() {
78 return !cmd.isEmpty();
79 }
80
81 /**
82 * Restarts the current Java application.
83 */
84 public static void restartJOSM() {
85 // If JOSM has been started with property 'josm.restart=true' this means
86 // it is executed by a start script that can handle restart.
87 // Request for restart is indicated by exit code 9.
88 String scriptRestart = getSystemProperty("josm.restart");
89 if ("true".equals(scriptRestart)) {
90 MainApplication.exitJosm(true, 9, SaveLayersDialog.Reason.RESTART);
91 }
92
93 // Log every related environmentvariable for debug purpose
94 if (isDebugSimulation()) {
95 logEnvironment();
96 }
97 Logging.info("Restart "+cmd);
98 if (isDebugSimulation()) {
99 Logging.debug("Restart cancelled to get debug info");
100 return;
101 }
102
103 // Leave early if restart is not possible
104 if (!isRestartSupported())
105 return;
106
107 // Initiate shutdown with a chance for user to cancel
108 if (!MainApplication.exitJosm(false, 0, SaveLayersDialog.Reason.RESTART))
109 return;
110
111 // execute the command in a shutdown hook, to be sure that all the
112 // resources have been disposed before restarting the application
113 Runtime.getRuntime().addShutdownHook(new Thread("josm-restarter") {
114 @Override
115 public void run() {
116 try {
117 Runtime.getRuntime().exec(cmd.toArray(new String[0]));
118 } catch (IOException e) {
119 Logging.error(e);
120 }
121 }
122 });
123 // exit
124 System.exit(0);
125 }
126
127 private static boolean isDebugSimulation() {
128 return Logging.isDebugEnabled() && Config.getPref().getBoolean("restart.debug.simulation");
129 }
130
131 private static void logEnvironment() {
132 logEnvironmentVariable("java.home");
133 logEnvironmentVariable("java.class.path");
134 logEnvironmentVariable("java.library.path");
135 logEnvironmentVariable("jnlpx.origFilenameArg");
136 logEnvironmentVariable("sun.java.command");
137 }
138
139 private static void logEnvironmentVariable(String var) {
140 Logging.debug("{0}: {1}", var, getSystemProperty(var));
141 }
142
143 private static boolean isExecutableFile(File f) {
144 try {
145 return f.isFile() && f.canExecute();
146 } catch (SecurityException e) {
147 Logging.error(e);
148 return false;
149 }
150 }
151
152 private static List<String> determineRestartCommands() {
153 try {
154 // special handling for OSX .app package (both legacy and jpackage-based)
155 if (PlatformManager.isPlatformOsx() && (
156 getSystemProperty("java.library.path").contains(APPLE_APP_PATH) ||
157 getSystemProperty("java.class.path").contains(APPLE_APP_PATH))) {
158 return getAppleCommands();
159 } else if (getSystemProperty("jpackage.app-path") != null) {
160 return Arrays.asList(getSystemProperty("jpackage.app-path"));
161 } else {
162 return getCommands();
163 }
164 } catch (IOException e) {
165 Logging.error(e);
166 return Collections.emptyList();
167 }
168 }
169
170 private static List<String> getAppleCommands() throws IOException {
171 if (!isExecutableFile(new File(APPLE_OSASCRIPT))) {
172 throw new IOException("Unable to find suitable osascript at " + APPLE_OSASCRIPT);
173 }
174 final List<String> cmd = new ArrayList<>();
175 cmd.add(APPLE_OSASCRIPT);
176 for (String line : RESTART_APPLE_SCRIPT.split("\n", -1)) {
177 cmd.add("-e");
178 cmd.add(line);
179 }
180 return cmd;
181 }
182
183 private static List<String> getCommands() throws IOException {
184 final List<String> cmd = new ArrayList<>();
185 // java binary
186 cmd.add(getJavaRuntime());
187 // vm arguments
188 addVMArguments(cmd);
189 // Determine webstart JNLP file. Use jnlpx.origFilenameArg instead of jnlp.application.href,
190 // because only this one is present when run from j2plauncher.exe (see #10795)
191 final String jnlp = getSystemProperty("jnlpx.origFilenameArg");
192 // program main and program arguments (be careful a sun property. might not be supported by all JVM)
193 final String javaCommand = getSystemProperty("sun.java.command");
194 if (javaCommand == null) {
195 throw new IOException("Unable to retrieve sun.java.command property");
196 }
197 String[] mainCommand = javaCommand.split(" ", -1);
198 if (javaCommand.endsWith(".jnlp") && jnlp == null) {
199 // see #11751 - jnlp on Linux
200 Logging.debug("Detected jnlp without jnlpx.origFilenameArg property set");
201 cmd.addAll(Arrays.asList(mainCommand));
202 } else {
203 // look for a .jar in all chunks to support paths with spaces (fix #9077)
204 StringBuilder sb = new StringBuilder(mainCommand[0]);
205 for (int i = 1; i < mainCommand.length && !mainCommand[i-1].endsWith(".jar"); i++) {
206 sb.append(' ').append(mainCommand[i]);
207 }
208 String jarPath = sb.toString();
209 // program main is a jar
210 if (jarPath.endsWith(".jar")) {
211 // if it's a jar, add -jar mainJar
212 cmd.add("-jar");
213 cmd.add(new File(jarPath).getPath());
214 } else {
215 // else it's a .class, add the classpath and mainClass
216 cmd.add("-cp");
217 cmd.add('"' + getSystemProperty("java.class.path") + '"');
218 cmd.add(mainCommand[0].replace("jdk.plugin/", "")); // Main class appears to be invalid on Java WebStart 9
219 }
220 // add JNLP file.
221 if (jnlp != null) {
222 cmd.add(jnlp);
223 }
224 }
225 // finally add program arguments
226 cmd.addAll(MainApplication.getCommandLineArgs());
227 return cmd;
228 }
229
230 private static String getJavaRuntime() throws IOException {
231 final String java = getSystemProperty("java.home") + File.separator + "bin" + File.separator +
232 (PlatformManager.isPlatformWindows() ? "java.exe" : "java");
233 if (!isExecutableFile(new File(java))) {
234 throw new IOException("Unable to find suitable java runtime at "+java);
235 }
236 return java;
237 }
238
239 private static void addVMArguments(Collection<String> cmd) {
240 List<String> arguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
241 Logging.debug("VM arguments: {0}", arguments);
242 for (String arg : arguments) {
243 // When run from jp2launcher.exe, jnlpx.remove is true, while it is not when run from javaws
244 // Always set it to false to avoid error caused by a missing jnlp file on the second restart
245 arg = arg.replace("-Djnlpx.remove=true", "-Djnlpx.remove=false");
246 // if it's the agent argument : we ignore it otherwise the
247 // address of the old application and the new one will be in conflict
248 if (!arg.contains("-agentlib")) {
249 cmd.add(arg);
250 }
251 }
252 }
253
254 /**
255 * Returns a new {@code ButtonSpec} instance that performs this action.
256 * @return A new {@code ButtonSpec} instance that performs this action.
257 */
258 public static ButtonSpec getRestartButtonSpec() {
259 return new ButtonSpec(
260 tr("Restart"),
261 ImageProvider.get("restart", ImageSizes.LARGEICON),
262 tr("Restart the application."),
263 ht("/Action/Restart"),
264 isRestartSupported()
265 );
266 }
267
268 /**
269 * Returns a new {@code ButtonSpec} instance that do not perform this action.
270 * @return A new {@code ButtonSpec} instance that do not perform this action.
271 */
272 public static ButtonSpec getCancelButtonSpec() {
273 return new ButtonSpec(
274 tr("Cancel"),
275 new ImageProvider("cancel"),
276 tr("Click to restart later."),
277 null /* no specific help context */
278 );
279 }
280
281 /**
282 * Returns default {@code ButtonSpec} instances for this action (Restart/Cancel).
283 * @return Default {@code ButtonSpec} instances for this action.
284 * @see #getRestartButtonSpec
285 * @see #getCancelButtonSpec
286 */
287 public static ButtonSpec[] getButtonSpecs() {
288 return new ButtonSpec[] {
289 getRestartButtonSpec(),
290 getCancelButtonSpec()
291 };
292 }
293}
Note: See TracBrowser for help on using the repository browser.