JAL-3820 Create and use 'Jalview app' symblinks to java executable. Fixes grouped...
[jalview.git] / src / jalview / bin / Launcher.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.bin;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.lang.management.ManagementFactory;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.concurrent.TimeUnit;
30
31 import jalview.util.ChannelProperties;
32 import jalview.util.LaunchUtils;
33
34 /**
35  * A Launcher class for Jalview. This class is used to launch Jalview from the
36  * shadowJar when Getdown is not used or available. It attempts to take all the
37  * command line arguments to pass on to the jalview.bin.Jalview class, but to
38  * insert a -Xmx memory setting to a sensible default, using the -jvmmempc and
39  * -jvmmemmax application arguments if specified. If not specified then system
40  * properties will be looked for by jalview.bin.MemorySetting. If the user has
41  * provided the JVM with a -Xmx setting directly and not set -jvmmempc or
42  * -jvmmemmax then this setting will be used and system properties ignored. If
43  * -Xmx is set as well as -jvmmempc or -jvmmemmax as argument(s) then the -Xmx
44  * argument will NOT be passed on to the main application launch.
45  * 
46  * @author bsoares
47  *
48  */
49 public class Launcher
50 {
51   private final static String startClass = "jalview.bin.Jalview";
52
53   private final static String dockIconPath = ChannelProperties
54           .getProperty("logo.512");
55
56   private static boolean checkJVMSymlink(String testBin)
57   {
58     File testBinFile = new File(testBin);
59     if (!testBinFile.exists())
60     {
61       return false;
62     }
63     File targetFile = null;
64     try
65     {
66       targetFile = testBinFile.getCanonicalFile();
67     } catch (IOException e)
68     {
69       return false;
70     }
71     if (targetFile != null && ("java".equals(targetFile.getName())
72             || "java.exe".equals(targetFile.getName())))
73     {
74       return true;
75     }
76     return false;
77   }
78
79   /**
80    * main method for jalview.bin.Launcher. This restarts the same JRE's JVM with
81    * the same arguments but with memory adjusted based on extracted -jvmmempc
82    * and -jvmmemmax application arguments. If on a Mac then extra dock:icon and
83    * dock:name arguments are also set.
84    * 
85    * @param args
86    */
87   public static void main(String[] args)
88   {
89     if (!LaunchUtils.checkJavaVersion())
90     {
91       System.err.println("WARNING - The Java version being used (Java "
92               + LaunchUtils.getJavaVersion()
93               + ") may lead to problems. This installation of Jalview should be used with Java "
94               + LaunchUtils.getJavaCompileVersion() + ".");
95     }
96     final String appName = ChannelProperties.getProperty("app_name");
97     final String javaBinDir = System.getProperty("java.home")
98             + File.separator + "bin" + File.separator;
99     String javaBin = null;
100     if (javaBin == null && checkJVMSymlink(javaBinDir + appName))
101     {
102       javaBin = javaBinDir + appName;
103     }
104     if (javaBin == null && checkJVMSymlink(javaBinDir + "Jalview"))
105     {
106       javaBin = javaBinDir + "Jalview";
107     }
108     if (javaBin == null)
109     {
110       javaBin = "java";
111     }
112
113     List<String> command = new ArrayList<>();
114     command.add(javaBin);
115
116     String memSetting = null;
117
118     boolean isAMac = System.getProperty("os.name").indexOf("Mac") > -1;
119
120     for (String jvmArg : ManagementFactory.getRuntimeMXBean()
121             .getInputArguments())
122     {
123       command.add(jvmArg);
124     }
125     command.add("-cp");
126     command.add(ManagementFactory.getRuntimeMXBean().getClassPath());
127
128     String jvmmempc = null;
129     String jvmmemmax = null;
130     boolean debug = false;
131     boolean wait = true;
132     boolean quiet = false;
133     // must set --debug before --launcher...
134     boolean launcherstop = false;
135     boolean launcherprint = false;
136     boolean launcherwait = false;
137     ArrayList<String> arguments = new ArrayList<>();
138     for (String arg : args)
139     {
140       if (arg.equals("--debug"))
141       {
142         debug = true;
143       }
144       if (arg.equals("--quiet"))
145       {
146         quiet = true;
147       }
148       if (debug && arg.equals("--launcherprint"))
149       {
150         launcherprint = true;
151       }
152       if (debug && arg.equals("--launcherstop"))
153       {
154         launcherstop = true;
155       }
156       if (debug && arg.equals("--launcherwait"))
157       {
158         launcherwait = true;
159       }
160       // this ends the launcher immediately
161       if (debug && arg.equals("--launchernowait"))
162       {
163         wait = false;
164       }
165       // Don't add the --launcher... args to Jalview launch
166       if (arg.startsWith("--launcher"))
167       {
168         continue;
169       }
170       // jvmmempc and jvmmemmax args used to set memory and are not passed on to
171       // startClass
172       if (arg.startsWith(
173               "-" + MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "="))
174       {
175         jvmmempc = arg.substring(
176                 MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.length()
177                         + 2);
178       }
179       else if (arg.startsWith(
180               "-" + MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME + "="))
181       {
182         jvmmemmax = arg.substring(
183                 MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME.length() + 2);
184       }
185       // --doubledash versions
186       else if (arg.startsWith("--"
187               + MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "="))
188       {
189         jvmmempc = arg.substring(
190                 MemorySetting.MAX_HEAPSIZE_PERCENT_PROPERTY_NAME.length()
191                         + 3);
192       }
193       else if (arg.startsWith(
194               "--" + MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME + "="))
195       {
196         jvmmemmax = arg.substring(
197                 MemorySetting.MAX_HEAPSIZE_PROPERTY_NAME.length() + 3);
198       }
199       // retain arg
200       else
201       {
202         arguments.add(arg);
203       }
204     }
205
206     // use saved preferences if no cmdline args
207     boolean useCustomisedSettings = LaunchUtils
208             .getBooleanUserPreference(MemorySetting.CUSTOMISED_SETTINGS);
209     if (useCustomisedSettings)
210     {
211       if (jvmmempc == null)
212       {
213         jvmmempc = LaunchUtils
214                 .getUserPreference(MemorySetting.MEMORY_JVMMEMPC);
215       }
216       if (jvmmemmax == null)
217       {
218         jvmmemmax = LaunchUtils
219                 .getUserPreference(MemorySetting.MEMORY_JVMMEMMAX);
220       }
221     }
222
223     // add these settings if not already specified
224     boolean memSet = false;
225     boolean dockIcon = false;
226     boolean dockName = false;
227     for (int i = 0; i < command.size(); i++)
228     {
229       String arg = command.get(i);
230       if (arg.startsWith("-Xmx"))
231       {
232         // only use -Xmx if jvmmemmax and jvmmempc have not been set
233         if (jvmmempc == null && jvmmemmax == null)
234         {
235           memSetting = arg;
236           memSet = true;
237         }
238       }
239       else if (arg.startsWith("-Xdock:icon"))
240       {
241         dockIcon = true;
242       }
243       else if (arg.startsWith("-Xdock:name"))
244       {
245         dockName = true;
246       }
247     }
248
249     if (!memSet)
250     {
251       long maxMemLong = MemorySetting.getMemorySetting(jvmmemmax, jvmmempc);
252
253       if (maxMemLong > 0)
254       {
255         memSetting = "-Xmx" + Long.toString(maxMemLong);
256         memSet = true;
257         command.add(memSetting);
258       }
259     }
260
261     if (isAMac)
262     {
263       if (!dockIcon)
264       {
265         command.add("-Xdock:icon=" + dockIconPath);
266       }
267       if (!dockName)
268       {
269         // -Xdock:name=... doesn't actually work :(
270         // Leaving it in in case it gets fixed
271         command.add("-Xdock:name=" + appName);
272         // this launches WITHOUT an icon in the macOS dock. Could be useful for
273         // getdown?
274         // command.add("-Dapple.awt.UIElement=false");
275         // This also does not work for the dock
276         command.add("-Dcom.apple.mrj.application.apple.menu.about.name="
277                 + appName);
278       }
279     }
280
281     String scalePropertyArg = HiDPISetting.getScalePropertyArg();
282     if (scalePropertyArg != null)
283     {
284       sysout(debug, quiet, "Running " + startClass + " with scale setting "
285               + scalePropertyArg);
286       command.add(scalePropertyArg);
287     }
288
289     command.add(startClass);
290     command.addAll(arguments);
291
292     final ProcessBuilder builder = new ProcessBuilder(command);
293
294     if ((Boolean.parseBoolean(System.getProperty("launcherprint", "false"))
295             || launcherprint))
296     {
297       sysout(debug, quiet,
298               "LAUNCHER COMMAND: " + String.join(" ", builder.command()));
299     }
300     sysout(debug, quiet,
301             "Running " + startClass + " with "
302                     + (memSetting == null ? "no memory setting"
303                             : ("memory setting " + memSetting)));
304
305     if (Boolean.parseBoolean(System.getProperty("launcherstop", "false"))
306             || (debug && launcherstop))
307     {
308       sysout(debug, quiet,
309               "System property 'launcherstop' is set and not 'false'. Exiting.");
310       System.exit(0);
311     }
312     try
313     {
314       builder.inheritIO();
315       Process process = builder.start();
316       if (wait || launcherwait)
317       {
318         sysout(debug, quiet, "Launching application process");
319         process.waitFor();
320       }
321       else
322       {
323         int waitInt = 0;
324         sysout(debug, quiet,
325                 "Wait time for application process is " + waitInt + "ms");
326         process.waitFor(waitInt, TimeUnit.MILLISECONDS);
327       }
328       sysout(debug, quiet, "Launcher process ending");
329     } catch (IOException e)
330     {
331       if (e.getMessage().toLowerCase(Locale.ROOT).contains("memory"))
332       {
333         System.err.println("Caught a memory exception: " + e.getMessage());
334         // Probably the "Cannot allocate memory" error, try without the memory
335         // setting
336         ArrayList<String> commandNoMem = new ArrayList<>();
337         for (int i = 0; i < command.size(); i++)
338         {
339           if (!command.get(i).startsWith("-Xmx"))
340           {
341             commandNoMem.add(command.get(i));
342           }
343         }
344         final ProcessBuilder builderNoMem = new ProcessBuilder(
345                 commandNoMem);
346         System.err.println("Command without memory setting: "
347                 + String.join(" ", builderNoMem.command()));
348         try
349         {
350           builderNoMem.inheritIO();
351           Process processNoMem = builderNoMem.start();
352           processNoMem.waitFor();
353         } catch (Exception ex)
354         {
355           ex.printStackTrace();
356         }
357       }
358       else
359       {
360         e.printStackTrace();
361       }
362     } catch (Exception e)
363     {
364       e.printStackTrace();
365     }
366   }
367
368   private static void sysout(boolean debug, boolean quiet, String message)
369   {
370     if (debug && !quiet)
371     {
372       System.out.println("LAUNCHERDEBUG - " + message);
373     }
374   }
375
376 }