JAL-3631 Substantial additions to Getdown (through jalview.util.LaunchUtils) to re...
authorBen Soares <b.soares@dundee.ac.uk>
Tue, 18 Jun 2024 20:36:44 +0000 (21:36 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Tue, 18 Jun 2024 20:36:44 +0000 (21:36 +0100)
18 files changed:
getdown/lib/getdown-core.jar
getdown/lib/getdown-launcher-local.jar
getdown/lib/getdown-launcher.jar
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/Application.java
getdown/src/getdown/core/src/main/java/com/threerings/getdown/data/EnvConfig.java
getdown/src/getdown/core/src/main/java/com/threerings/getdown/util/LaunchUtil.java
getdown/src/getdown/core/src/main/java/jalview/bin/Console.java [deleted file]
getdown/src/getdown/core/src/main/java/jalview/util/ErrorLog.java
getdown/src/getdown/core/src/main/java/jalview/util/LaunchUtils.java
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/Getdown.java
getdown/src/getdown/launcher/src/main/java/com/threerings/getdown/launcher/GetdownApp.java
getdown/src/getdown/mvn_cmd
j11lib/getdown-core.jar
j8lib/getdown-core.jar
src/jalview/bin/Launcher.java
src/jalview/util/ErrorLog.java
src/jalview/util/LaunchUtils.java
utils/install4j/install4j10_template.install4j

index 88464c3..746765e 100644 (file)
Binary files a/getdown/lib/getdown-core.jar and b/getdown/lib/getdown-core.jar differ
index 167455d..a913f9b 100644 (file)
Binary files a/getdown/lib/getdown-launcher-local.jar and b/getdown/lib/getdown-launcher-local.jar differ
index d4fc05d..2414291 100644 (file)
Binary files a/getdown/lib/getdown-launcher.jar and b/getdown/lib/getdown-launcher.jar differ
index cb51ed6..d5a3def 100644 (file)
@@ -2068,9 +2068,6 @@ public class Application
     }
     
     protected static boolean copyApplicationAppDirToUserAppDir(String applicationAppDirName, String userAppDirName) {
-      System.out.println("##### About to run copyApplicationAppDirToUserAppDir");
-      System.out.println("##### applicationAppDirName='"+applicationAppDirName+"'");
-      System.out.println("##### userAppDirName='"+userAppDirName+"'");
       if (applicationAppDirName == null || userAppDirName == null) {
         log.warning("Null parameter", "applicationAppDirName", applicationAppDirName, "userAppDirName", userAppDirName);
         return false;
@@ -2130,20 +2127,19 @@ public class Application
         log.warning("Problem opening application digest files", new File(applicationAppDir, Digest.digestFile(Digest.VERSION)));
         return false;
       }
-      // copy getdown.txt, digest.txt and digest2.txt, getdown-launcher.jar
+      // copy getdown.txt, digest.txt and digest2.txt, getdown-launcher.jar, channel.props
       File getdownLauncher = new File(applicationAppDir, "getdown-launcher.jar");
-      for (File from: new File[] {configFile, digest2.getFile(), digest.getFile(), getdownLauncher}) {
+      for (File from: new File[] {configFile, digest2.getFile(), digest.getFile(), getdownLauncher, new File(applicationAppDir, "channel.props")}) {
         try {
           File to = new File(userAppDir, from.getName());
           Files.copy(from.toPath(), to.toPath());
         } catch (IOException e) {
-          log.warning("Couldn't copy config/digest file", from);
+          log.warning("Couldn't copy config/digest/getdown-launcher/channel file", from);
         }
       }
       
       MessageDigest md = Digest.getMessageDigest(Digest.VERSION);
       for (Resource rsc: copyResources) {
-        System.out.println("##### resource: "+rsc.toString());
         String digestHash = digest2.getDigest(rsc);
         String actualHash;
         try {
@@ -2152,8 +2148,6 @@ public class Application
           log.warning("Failed digest hash creation for resource", rsc);
           continue;
         }
-        System.out.println("####### digest "+rsc.getPath()+": "+digestHash);
-        System.out.println("####### actual "+rsc.getPath()+": "+actualHash);
         if (digestHash == null || actualHash == null) {
           log.warning("Something went wrong with digest hashes", digestHash, actualHash);
           continue;
@@ -2174,7 +2168,6 @@ public class Application
         try {
           Files.copy(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
           log.info("Copying resource file", from, to);
-          System.out.println("##### Copying file '"+from.toString()+" to "+to.toString());
           try {
             rsc.applyAttrs();
           } catch(IOException e2) {
@@ -2198,11 +2191,8 @@ public class Application
         for (String resourcePath : rsrcs) {
             try {
                 list.add(new Resource(resourcePath, null, new File(applicationAppDir, resourcePath), executable? Resource.EXEC : Resource.NORMAL));
-                log.info("##### Adding resource to list", resourcePath);
-                //System.out.println("##### Adding resource to list"+ resourcePath);
             } catch (Exception e) {
                 log.warning("Invalid resource '" + resourcePath + "'. " + e);
-                //System.out.println("##### Invalid resource "+ resourcePath);
             }
         }
     }
index a10c67b..3780e6c 100644 (file)
@@ -7,8 +7,10 @@ package com.threerings.getdown.data;
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.security.MessageDigest;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
@@ -19,6 +21,7 @@ import com.threerings.getdown.util.StringUtil;
 
 import jalview.util.ChannelProperties;
 import jalview.util.HttpUtils;
+import jalview.util.LaunchUtils;
 
 import com.threerings.getdown.data.Application;
 
@@ -58,8 +61,10 @@ public final class EnvConfig {
      * configuration source.
      */
     public static EnvConfig create (String[] argv, List<Note> notes) {
-        System.out.println("##### out Starting EnvConfig.create()");
-        System.err.println("##### err Starting EnvConfig.create()");
+      return create(argv, notes, null);
+    }
+    
+    public static EnvConfig create (String[] argv, List<Note> notes, Class startClass) {
         String appDir = null;
         String appDirProv = null;
         String appId = null;
@@ -67,7 +72,8 @@ public final class EnvConfig {
         String appBase = null;
         String appBaseProv = null;
         applicationFolder = System.getProperty("installer.application_folder");
-        installerAppdir = System.getProperty("installer.appdir");
+        installerAppdir = System.getProperty(APPLICATION_APPDIR_PROPERTY);
+        
         appName = System.getProperty("channel.app_name");
         
         // start with bootstrap.properties config, if avaialble
@@ -159,12 +165,58 @@ public final class EnvConfig {
         // platform user appdir addition
         if (StringUtil.isBlank(appDir)) {
           appDir = getUserAppdir();
-          appDirProv = "user default";
-          userAppDir = appDir != null;
+          if (appDir != null) {
+            appDirProv = "user default";
+            userAppDir = true;
+          }
         }
-        
-        if (SysProps.noUpdate() && Boolean.valueOf(System.getProperty("no" + USE_DEFAULT_APPDIR_PROPERTY)) && appDir == null) {
-          appDir = System.getProperty(APPLICATION_APPDIR_PROPERTY);
+        // forced by system property to not use user default appdir
+        if (SysProps.noUpdate() && Boolean.valueOf(System.getProperty("no" + USER_DEFAULT_APPDIR_PROPERTY))) {
+          appDir = installerAppdir;
+          appDirProv = "no user default";
+          userAppDir = false;
+        } else if (userAppDir && installerAppdir != null) {
+          // we're going to try and use a different appdir, check if newer getdown-launcher.jar found there, and restart using that one
+          final String getdown = "getdown-launcher.jar";
+          File appdirGetdown = new File(appDir, getdown);
+          if (appdirGetdown.exists()) {
+            File installerGetdown = new File(installerAppdir, getdown);
+            try {
+              Resource appdirGetdownResource = new Resource(appdirGetdown.getAbsolutePath(), null, appdirGetdown, Resource.NORMAL);
+              Resource installerGetdownResource = new Resource(installerGetdown.getAbsolutePath(), null, installerGetdown, Resource.NORMAL);
+              MessageDigest md = Digest.getMessageDigest(1);
+              String appdirGetdownDigest = appdirGetdownResource.computeDigest(1, md, null);
+              String installerGetdownDigest = installerGetdownResource.computeDigest(1, md, null);
+              if (!appdirGetdownDigest.equals(installerGetdownDigest)) { // not the same file content 
+                // change classpath and restart.  A null startClass looks it up from system property "sun.java.command"
+                String javaBin = LaunchUtils.findJavaBin(false);
+                List<String> removeClasspath = new ArrayList<>();
+                removeClasspath.add(installerGetdown.getAbsolutePath());
+                List<String> prependClasspath = new ArrayList<>();
+                prependClasspath.add(appdirGetdown.getAbsolutePath());
+                // ensure the appdir and appid are also added as command line arguments for backwards compatibility (earlier versions of getdown-launcher.jar)
+                List<String> removeArgs = new ArrayList<>();
+                removeArgs.add(argvAppDir);
+                removeArgs.add(argvAppId);
+                List<String> addArgs = new ArrayList<>();
+                addArgs.add(appDir);
+                addArgs.add(appId);
+                String startClassName = startClass == null ? null : startClass.getName();
+                int exitValue = LaunchUtils.startNewJvm(javaBin, null, null, prependClasspath, null, removeClasspath, startClassName, removeArgs, addArgs, Arrays.asList(argv), true, true, false, true, false); 
+                
+                if (exitValue == 0) {
+                  notes.add(Note.info("Relaunching getdown succeeded.  Original getdown exiting now."));
+                  setRelaunched(true);
+                  return null;
+                } else {
+                  notes.add(Note.warn("Relaunching getdown did not seem to succeed with exit value " + exitValue + ".  Continuing with this getdown process."));
+                }
+              }
+            } catch (IOException e) {
+              notes.add(Note.warn("IOException opening '" + appdirGetdown.getAbsolutePath() + "' and '" + installerGetdown.getAbsolutePath() + "' as Resources. Continuing without restart. " + e.getMessage()));
+            }
+          }
         }
         
         int skipArgs = 2;
@@ -185,23 +237,19 @@ public final class EnvConfig {
         }
 
         notes.add(Note.info("Using appdir from " + appDirProv + ": " + appDir));
-        System.out.println(Note.info("##### Using appdir from " + appDirProv + ": " + appDir));
         if (appId != null) notes.add(Note.info("Using appid from " + appIdProv + ": " + appId));
         if (appBase != null) {
           notes.add(
             Note.info("Using appbase from " + appBaseProv + ": " + appBase));
-            System.out.println("##### Using appbase from " + appBaseProv + ": " + appBase);
         }
 
         // ensure that the appdir refers to a directory that exists
         File appDirFile = new File(appDir);
         if (!appDirFile.exists()) {
-            System.out.println("##### appDir '"+appDir+"' doesn't exist");
             // if we have a bootstrap URL then we auto-create the app dir; this enables an
             // installer to simply place a getdown.jar file somewhere and create an OS shortcut
             // that runs getdown with an appdir and appbase specified, and have getdown create the
             // appdir and download the app into it
-            System.out.println("##### appBase='"+appBase+"'");
             if (!StringUtil.isBlank(appBase)) {
                 if (appDirFile.mkdirs()) {
                     notes.add(Note.info("Auto-created app directory '" + appDir + "'"));
@@ -209,11 +257,9 @@ public final class EnvConfig {
                     notes.add(Note.warn("Unable to auto-create app dir: '" + appDir + "'"));
                 }
             } else if (userAppDir && Boolean.valueOf(System.getProperty(POPULATE_DEFAULT_APPDIR_PROPERTY))) {
-                System.out.println("##### Created appDir='"+appDir+"'");
                 appBase = System.getProperty(APPLICATION_APPDIR_PROPERTY);
                 Application.copyApplicationAppDirToUserAppDir(System.getProperty(APPLICATION_APPDIR_PROPERTY), appDir);
             } else {
-                System.out.println("##### Invalid appDir='"+appDir+"' and no appBase set");
                 notes.add(Note.error("Invalid appdir '" + appDir + "': directory does not exist"));
                 return null;
             }
@@ -277,34 +323,51 @@ public final class EnvConfig {
     }
     
     private static final String getUserAppdir() {
-      System.out.println("##### Property '" + USE_DEFAULT_APPDIR_PROPERTY + "' is '" + System.getProperty(USE_DEFAULT_APPDIR_PROPERTY) + "'");
-      final String noUseDefaultAppDirProperty = "no" + USE_DEFAULT_APPDIR_PROPERTY;
+      final String noUseDefaultAppDirProperty = "no" + USER_DEFAULT_APPDIR_PROPERTY;
       if (Boolean.valueOf(System.getProperty(noUseDefaultAppDirProperty))) {
         System.err.println("Not using default user appdir because property '" + noUseDefaultAppDirProperty + "' is '" + System.getProperty(noUseDefaultAppDirProperty) + "'");
         return null;
       }
-      if (!Boolean.valueOf(System.getProperty(USE_DEFAULT_APPDIR_PROPERTY))) {
-        System.err.println("Not using default user appdir because property '" + USE_DEFAULT_APPDIR_PROPERTY + "' is '" + System.getProperty(USE_DEFAULT_APPDIR_PROPERTY) + "'");
+      if (!Boolean.valueOf(System.getProperty(USER_DEFAULT_APPDIR_PROPERTY))) {
+        System.err.println("Not using default user appdir because property '" + USER_DEFAULT_APPDIR_PROPERTY + "' is '" + System.getProperty(USER_DEFAULT_APPDIR_PROPERTY) + "'");
         return null;
       }
-      String appdirname = installerAppdir == null || installerAppdir.length() == 0 ? ChannelProperties.FALLBACK_APPNAME : installerAppdir;
-      String userAppDataPath;
+      String appdirname = applicationFolder == null || applicationFolder.length() == 0 ? ChannelProperties.FALLBACK_APPNAME : applicationFolder;
       String home = System.getProperty("user.home");
       String appname = StringUtil.isBlank(appName) ? ChannelProperties.FALLBACK_APPNAME : appName;
       final String FS = File.separator;
+      String appDataPath;
+      String append;
       if (LaunchUtil.isMacOS()) {
-        userAppDataPath = home + FS + "Library" + FS + "Application Support" + FS + "Jalview-Desktop" + FS + appname + FS + "app";
+        appDataPath = osAppDataPathMap.get("macos");
+        append = appname;
       } else if (LaunchUtil.isWindows()) {
-        userAppDataPath = home + FS + "AppData" + FS + "Local" + FS + "Jalview-Desktop" + FS + appdirname + FS + "app";
+        appDataPath = osAppDataPathMap.get("windows");
+        append = appdirname;
       } else if (LaunchUtil.isLinux()) {
-        userAppDataPath = home + FS + ".local" + FS + "share" + FS + "jalview-desktop" + FS + appdirname.toLowerCase(Locale.ROOT) + FS + "app";
+        appDataPath = osAppDataPathMap.get("linux");
+        append = appdirname.toLowerCase(Locale.ROOT);
       } else {
-        userAppDataPath = home + FS + ".jalview-desktop" + FS + appdirname.toLowerCase(Locale.ROOT) + FS + "app";
+        appDataPath = osAppDataPathMap.get("other");
+        append = appdirname.toLowerCase(Locale.ROOT);
+      }
+      if (!"/".equals(FS)) {
+        appDataPath = appDataPath.replaceAll("/", FS);
       }
-      System.out.println("##### About to return userAppDataPath value '"+userAppDataPath+"'");
-      return userAppDataPath;
+      String returnString = home + FS + appDataPath + FS + append + FS + "app";
+      return returnString;
+    }
+    
+    public static void setRelaunched(boolean b) {
+      relaunched = b;
+    }
+
+    public static boolean getRelaunched() {
+      return relaunched;
     }
 
+    private static boolean relaunched = false;
+    
     private static final String USER_HOME_KEY = "${user.home}";
     
     private static String applicationFolder = null;
@@ -313,9 +376,20 @@ public final class EnvConfig {
     
     private static String appName = null;
     
-    private static final String USE_DEFAULT_APPDIR_PROPERTY = "usedefaultappdir";
+    private static final String USER_DEFAULT_APPDIR_PROPERTY = "userdefaultappdir";
     
     private static final String APPLICATION_APPDIR_PROPERTY = "installer.appdir";
     
     private static final String POPULATE_DEFAULT_APPDIR_PROPERTY= "populatedefaultappdir";
+    
+    private static final Map<String,String> osAppDataPathMap;
+    
+    static {
+      // paths from user.home with application folder added to end. '/' replaced with file.separator
+      osAppDataPathMap = new HashMap<>();
+      osAppDataPathMap.put("macos", "Library/Application Support/Jaview-Desktop");
+      osAppDataPathMap.put("linux", ".local/share/jalview-desktop");
+      osAppDataPathMap.put("windows", "AppData/Local/Jalview-Desktop");
+      osAppDataPathMap.put("other", ".jalview-desktop");
+    }
 }
index 3b81fbb..d6ad03d 100644 (file)
@@ -104,20 +104,20 @@ public class LaunchUtil
         }
         
         // first look in our application directory for an installed VM
-        final String appDir = isMacOS() ?
+        final String appJreDir = isMacOS() ?
                         (new File(appdir, LOCAL_JAVA_DIR).getAbsolutePath()) + "/Contents/Home"
                         : new File(appdir, LOCAL_JAVA_DIR).getAbsolutePath();
 
-        String javaBin = LaunchUtils.findJavaBin(appDir, windebug, false);
+        String javaBin = LaunchUtils.findJavaBin(appJreDir, windebug, true, false);
 
         // then fall back to the VM in which we're already running
         if (javaBin == null) {
-            javaBin = LaunchUtils.findJavaBin(System.getProperty("java.home"), windebug, false);
+            javaBin = LaunchUtils.findJavaBin(System.getProperty("java.home"), windebug, true, false);
         }
 
         // then throw up our hands and hope for the best
         if (javaBin == null) {
-            javaBin = LaunchUtils.findJavaBin(null, windebug, true);
+            javaBin = LaunchUtils.findJavaBin(null, windebug, false, true);
             log.warning("Unable to find java [appdir=" + appdir +
                         ", java.home=" + System.getProperty("java.home") + "]!");
         }
diff --git a/getdown/src/getdown/core/src/main/java/jalview/bin/Console.java b/getdown/src/getdown/core/src/main/java/jalview/bin/Console.java
deleted file mode 100644 (file)
index 66fc865..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package jalview.bin;
-
-public class Console {
-  public static boolean initLogger() {
-    return false;
-  }
-  public static void outPrintln(String s) {
-  }
-  public static void errPrintln(String s) {
-  }
-}
index e94b59e..bbc758b 100644 (file)
@@ -1,9 +1,32 @@
 package jalview.util;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 public class ErrorLog
 {
   private static boolean hasConsole = true;
 
+  private static Class console = null;
+
+  private static Method initLogger = null;
+
+  private static Method errPrintln = null;
+
+  private static Method outPrintln = null;
+
+  private static String prefix = null;
+
+  public static void setHasConsole(boolean b)
+  {
+    hasConsole = b;
+  }
+
+  public static void setPrefix(String s)
+  {
+    prefix = s;
+  }
+
   public static void outPrintln(String message)
   {
     println(message, false);
@@ -16,22 +39,64 @@ public class ErrorLog
 
   public static void println(String message, boolean err)
   {
-    if (hasConsole)
+    println(message, err, true);
+  }
+
+  public static void println(String message0, boolean err,
+          boolean thisHasConsole)
+  {
+    String message = prefix == null ? message0 : prefix + message0;
+    if (thisHasConsole && hasConsole)
     {
       try
       {
-        hasConsole = jalview.bin.Console.initLogger();
-        if (hasConsole)
+        if (console == null)
+        {
+          Class console = Class.forName("jalview.bin.Console");
+        }
+        if (console == null)
+        {
+          hasConsole = false;
+        }
+        else
         {
-          if (err)
+          if (initLogger == null && console != null)
           {
-            jalview.bin.Console.errPrintln(message);
+            initLogger = console.getMethod("initLogger");
           }
-          else
+          hasConsole = console == null || initLogger == null
+                  || (Boolean) initLogger.invoke(null);
+          if (hasConsole && console != null)
           {
-            jalview.bin.Console.outPrintln(message);
+            if (err)
+            {
+              if (errPrintln == null)
+              {
+                errPrintln = console.getMethod("errPrintln", String.class);
+              }
+              errPrintln.invoke(null, message);
+            }
+            else
+            {
+              if (outPrintln == null)
+              {
+                outPrintln = console.getMethod("outPrintln", String.class);
+              }
+              outPrintln.invoke(null, message);
+            }
           }
         }
+      } catch (ClassNotFoundException | NoSuchMethodException e)
+      {
+        hasConsole = false;
+        System.err.println(
+                "jalview.util.ErrorLog has no jalview.bin.Console.initLogger(). Using System.err and System.out.");
+      } catch (IllegalAccessException | IllegalArgumentException
+              | InvocationTargetException e)
+      {
+        hasConsole = false;
+        System.err.println(
+                "jalview.util.ErrorLog had a problem calling a method of jalview.bin.Console.  Using System.err and System.out.");
       } catch (Exception e)
       {
         e.printStackTrace();
@@ -42,7 +107,7 @@ public class ErrorLog
                 "jalview.util.ErrorLog has no jalview.bin.Console. Using System.err and System.out.");
       }
     }
-    if (!hasConsole)
+    if (!(thisHasConsole && hasConsole))
     {
       if (err)
       {
index f8cc269..66c983c 100644 (file)
@@ -25,9 +25,18 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.management.ManagementFactory;
 import java.net.MalformedURLException;
+import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
 import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 public class LaunchUtils
 {
@@ -112,7 +121,8 @@ public class LaunchUtils
               null);
       if (JCV == null)
       {
-        ErrorLog.errPrintln("Could not obtain JAVA_COMPILE_VERSION for comparison");
+        ErrorLog.errPrintln(
+                "Could not obtain JAVA_COMPILE_VERSION for comparison");
         return -2;
       }
       JAVA_COMPILE_VERSION = Integer.parseInt(JCV);
@@ -167,6 +177,19 @@ public class LaunchUtils
     return JAVA_VERSION;
   }
 
+  public static String getJarPath(Class c)
+  {
+    try
+    {
+      return c.getProtectionDomain().getCodeSource().getLocation().toURI()
+              .getPath();
+    } catch (URISyntaxException e)
+    {
+      ErrorLog.errPrintln("Problem with class source location");
+      return null;
+    }
+  }
+
   public static boolean checkJavaVersion()
   {
     if (LaunchUtils.isJS)
@@ -197,20 +220,29 @@ public class LaunchUtils
 
   public static String findJavaBin(boolean winConsole)
   {
-    return findJavaBin(System.getProperty("java.home"), winConsole, true);
+    return findJavaBin(System.getProperty("java.home"), winConsole, true,
+            true);
+  }
+
+  public static String findJavaBin(boolean winConsole,
+          boolean applicationName, boolean generic)
+  {
+    return findJavaBin(System.getProperty("java.home"), winConsole,
+            applicationName, generic);
   }
 
   /*
    * Returns a string path to the most likely java binary wanted to run this
    * installation of Jalview.
    * 
+   * @param  javaHome    Try this javaHome dir (defaults to the running java.home).
    * @param  winConsole  whether to use java.exe (console) in preference to javaw.exe
    *                     (only affects Windows).
-   * @param  javaHome    Try this javaHome dir (defaults to the running java.home).
+   * @param  applicationName  Look to see if the Jalview application name symbolic link is present and use it.
    * @param  generic     Return a generic java command if not found.
    */
   public static String findJavaBin(String javaHome, boolean winConsole,
-          boolean generic)
+          boolean applicationName, boolean generic)
   {
     String javaBin = null;
     final String javaExe = winConsole ? "java.exe" : "javaw.exe";
@@ -218,11 +250,16 @@ public class LaunchUtils
 
     if (javaHome != null)
     {
+      String propertyAppName = null;
+      String appName = null;
       // property "channel.app_name" is set by install4j when launching getdown
-      String propertyAppName = System.getProperty("channel.app_name");
-      final String appName = (propertyAppName != null
-              && propertyAppName.length() > 0) ? propertyAppName
-                      : ChannelProperties.getProperty("app_name");
+      if (applicationName)
+      {
+        propertyAppName = System.getProperty("channel.app_name");
+        appName = (propertyAppName != null && propertyAppName.length() > 0)
+                ? propertyAppName
+                : ChannelProperties.getProperty("app_name");
+      }
 
       final String javaBinDir = javaHome + File.separator + "bin"
               + File.separator;
@@ -230,8 +267,26 @@ public class LaunchUtils
       // appName and "Jalview" will not point to javaw.exe or java.exe but in
       // this case that's okay because the taskbar display name problem doesn't
       // manifest in Windows. See JAL-3820, JAL-4189.
-      for (String name : new String[] { appName, ChannelProperties.FALLBACK_APPNAME, java, javaExe })
+      List<String> potentialJavaBin = new ArrayList<>();
+      if (applicationName)
       {
+        if (appName != null)
+        {
+          potentialJavaBin.add(appName);
+        }
+        if (ChannelProperties.FALLBACK_APPNAME != null)
+        {
+          potentialJavaBin.add(ChannelProperties.FALLBACK_APPNAME);
+        }
+      }
+      potentialJavaBin.add(java);
+      potentialJavaBin.add(javaExe);
+      for (String name : potentialJavaBin)
+      {
+        if (name == null)
+        {
+          continue;
+        }
         if (LaunchUtils.checkJVMSymlink(javaBinDir + name, winConsole))
         {
           javaBin = javaBinDir + name;
@@ -278,4 +333,260 @@ public class LaunchUtils
     }
     return false;
   }
+
+  /**
+   * Create a java command that matches the currently running java process and
+   * optionally remove/add some JVM and application parameters.
+   * 
+   * @param String
+   *          javaBinary The java binary to use. null uses the same as current
+   *          process.
+   * @param String[]
+   *          removeJvmArguments The (start of) JVM arguments to remove.
+   * @param String[]
+   *          addJvmArguments JVM arguments to add.
+   * @param String[]
+   *          prependToClasspath Add these dirs to the start of the classpath
+   * @param String[]
+   *          appendToClasspath Add these dirs to the end of the classpath
+   * @param String[]
+   *          deleteFromClasspath Remove these dirs from the existing classpath
+   * @param String
+   *          startClass The name of the start class if different. null if the
+   *          same.
+   * @param String[]
+   *          removeAppArguments The (start of) application arguments to remove.
+   * @param String[]
+   *          addAppArguments Application arguments to add.
+   * @param boolean
+   *          terminate Flag to terminate this process after starting new
+   *          process.
+   */
+  public static int startNewJvm(String javaBinary,
+          List<String> removeJvmArguments, List<String> addJvmArguments,
+          List<String> prependToClasspath, List<String> appendToClasspath,
+          List<String> removeFromClasspath, String startClass,
+          List<String> removeAppArguments, List<String> addAppArguments,
+          List<String> appArguments, boolean launcherprint,
+          boolean launcherwait, boolean launcherstop, boolean debug,
+          boolean quiet)
+  {
+    int exitValue = -1;
+    if (javaBinary == null)
+    {
+      javaBinary = findJavaBin(false, true, true);
+    }
+
+    List<String> classpathDirs = new ArrayList<>();
+    if (prependToClasspath != null)
+    {
+      classpathDirs.addAll(prependToClasspath);
+    }
+
+    String classpath = ManagementFactory.getRuntimeMXBean().getClassPath();
+    if (removeFromClasspath != null)
+    {
+      Set<String> removeCp = new HashSet<>();
+      for (String dcp : removeFromClasspath)
+      {
+        try
+        {
+          String canPath = new File(dcp).getCanonicalPath();
+          removeCp.add(canPath);
+        } catch (IOException e)
+        {
+          ErrorLog.errPrintln(
+                  "Problem getting canonical path. " + e.getMessage());
+        }
+      }
+      for (String cp : classpath.split(File.pathSeparator))
+      {
+        try
+        {
+          String canPath = new File(cp).getCanonicalPath();
+          if (!removeCp.contains(canPath))
+          {
+            classpathDirs.add(cp);
+          }
+        } catch (IOException e)
+        {
+          ErrorLog.errPrintln(
+                  "Problem getting canonical path. " + e.getMessage());
+        }
+      }
+    }
+    else
+    {
+      classpathDirs
+              .addAll(Arrays.asList(classpath.split(File.pathSeparator)));
+    }
+    if (appendToClasspath != null)
+    {
+      classpathDirs.addAll(appendToClasspath);
+    }
+
+    List<String> jvmArguments = new ArrayList<>();
+    List<String> originalJvmArguments = ManagementFactory.getRuntimeMXBean()
+            .getInputArguments();
+    if (removeJvmArguments != null)
+    {
+      for (String jvmArg : originalJvmArguments)
+      {
+        boolean addArg = true;
+        for (String rmArg : removeJvmArguments)
+        {
+          if (jvmArg.startsWith(rmArg))
+          {
+            addArg = false;
+            break;
+          }
+        }
+        if (addArg)
+        {
+          jvmArguments.add(jvmArg);
+        }
+      }
+    }
+    else
+    {
+      jvmArguments.addAll(originalJvmArguments);
+    }
+    if (addJvmArguments != null)
+    {
+      jvmArguments.addAll(addJvmArguments);
+    }
+
+    if (startClass == null)
+    {
+      // this isn't always reliable
+      startClass = System.getProperty("sun.java.command");
+    }
+
+    List<String> applicationArguments = new ArrayList<>();
+    if (removeAppArguments != null)
+    {
+      Set<String> removeArgs = new HashSet<>(removeAppArguments);
+      for (String appArg : appArguments)
+      {
+        if (!removeArgs.contains(removeArgs))
+        {
+          applicationArguments.add(appArg);
+        }
+      }
+    }
+    else
+    {
+      applicationArguments.addAll(appArguments);
+    }
+    if (addAppArguments != null)
+    {
+      applicationArguments.addAll(addAppArguments);
+    }
+
+    List<String> command = new ArrayList<>();
+    // java command
+    command.add(javaBinary);
+
+    // classpath
+    command.add("-cp");
+    command.add(String.join(File.pathSeparator, classpathDirs));
+
+    // jvm args
+    command.addAll(jvmArguments);
+
+    // start class
+    command.add(startClass);
+
+    // application args
+    command.addAll(applicationArguments);
+
+    final ProcessBuilder builder = new ProcessBuilder(command);
+
+    if (Boolean.parseBoolean(System.getProperty("launcherprint", "false"))
+            || launcherprint)
+    {
+      syserr(debug, quiet,
+              "COMMAND: " + String.join(" ", builder.command()));
+    }
+
+    if (Boolean.parseBoolean(System.getProperty("launcherstop", "false"))
+            || (debug && launcherstop))
+    {
+      syserr(debug, quiet,
+              "System property 'launcherstop' is set and not 'false'. Exiting.");
+      System.exit(0);
+    }
+    try
+    {
+      builder.inheritIO();
+      Process process = builder.start();
+      if (launcherwait)
+      {
+        syserr(debug, quiet, "Launching application process");
+        exitValue = process.waitFor();
+        syserr(debug, quiet,
+                "Application process return with value " + exitValue);
+      }
+      else
+      {
+        int waitInt = 0;
+        syserr(debug, quiet,
+                "Wait time for application process is " + waitInt + "ms");
+        if (process.waitFor(waitInt, TimeUnit.MILLISECONDS))
+        {
+          exitValue = process.exitValue();
+        }
+        else
+        {
+          exitValue = -2;
+        }
+      }
+      syserr(debug, quiet, "Launcher process ending");
+    } catch (IOException e)
+    {
+      if (e.getMessage().toLowerCase(Locale.ROOT).contains("memory"))
+      {
+        syserr(true, quiet, "Caught a memory exception: " + e.getMessage());
+        // Probably the "Cannot allocate memory" error, try without the memory
+        // setting
+        ArrayList<String> commandNoMem = new ArrayList<>();
+        for (int i = 0; i < command.size(); i++)
+        {
+          if (!command.get(i).startsWith("-Xmx"))
+          {
+            commandNoMem.add(command.get(i));
+          }
+        }
+        final ProcessBuilder builderNoMem = new ProcessBuilder(
+                commandNoMem);
+        syserr(true, quiet, "Command without memory setting: "
+                + String.join(" ", builderNoMem.command()));
+        try
+        {
+          builderNoMem.inheritIO();
+          Process processNoMem = builderNoMem.start();
+          exitValue = processNoMem.waitFor();
+        } catch (Exception ex)
+        {
+          ex.printStackTrace();
+        }
+      }
+      else
+      {
+        e.printStackTrace();
+      }
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+    return exitValue;
+  }
+
+  public static void syserr(boolean debug, boolean quiet, String message)
+  {
+    if (debug && !quiet)
+    {
+      ErrorLog.errPrintln("DEBUG - " + message);
+    }
+  }
 }
index 200e743..0be4101 100644 (file)
@@ -1061,6 +1061,8 @@ public abstract class Getdown extends Thread
             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
             String line;
             while ((line = reader.readLine()) != null) {
+                out.println(line);
+                out.flush();
                 // check for desktop creation line and end early
                 if (!_disposed && line.endsWith("JALVIEW: CREATED DESKTOP")) {
                   // pump the percent up to 100%
@@ -1074,8 +1076,6 @@ public abstract class Getdown extends Thread
                     // let nature take its course
                   }
                 }
-                out.println(line);
-                out.flush();
             }
         } catch (IOException ioe) {
             log.warning("Failure copying", "in", in, "out", out, "error", ioe);
index 87ed8d2..b52363a 100644 (file)
@@ -43,6 +43,7 @@ import jalview.util.HttpUtils;
 public class GetdownApp
 {
   public static List<String> startupFilesParameters = new ArrayList<>();
+  
   /**
    * The main entry point of the Getdown launcher application.
    */
@@ -61,12 +62,19 @@ public class GetdownApp
    * @throws Exception if anything goes wrong starting Getdown.
    */
   public static Getdown start (String[] argv) throws Exception {
+    jalview.util.ErrorLog.setHasConsole(false);
+    jalview.util.ErrorLog.setPrefix("GETDOWN - ");
     List<EnvConfig.Note> notes = new ArrayList<>();
-    EnvConfig envc = EnvConfig.create(argv, notes);
+    EnvConfig envc = EnvConfig.create(argv, notes, GetdownApp.class);
     if (envc == null) {
-      if (!notes.isEmpty()) for (EnvConfig.Note n : notes) System.err.println(n.message);
-      else System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]");
-      System.exit(-1);
+      if (!notes.isEmpty()) {
+        for (EnvConfig.Note n : notes) {
+          System.err.println(n.message);
+        }
+      } else {
+        System.err.println("Usage: java -jar getdown.jar [app_dir] [app_id] [app args]");
+      }
+      System.exit(EnvConfig.getRelaunched() ? 0 : -1);
     }
 
     // pipe our output into a file in the application directory
index 59cb761..52b4b40 100755 (executable)
@@ -12,6 +12,18 @@ fi
 
 echo "Setting VERSION to '$VERSION'"
 perl -p -i -e 's|(<version>)[^<]*JVL[^<]*(</version>)|${1}$ENV{VERSION}${2}|;' pom.xml */pom.xml
+
+echo "Making sure jalview classes are up-to-date"
+for x in $(find core/src/main/java/jalview launcher/src/main/java/jalview -name "*.java")
+do
+  y=${x##*java/}
+  if [ -e "../../../src/${y}" ]; then
+    echo /bin/cp "../../../src/${y}" "${x}"
+    /bin/cp "../../../src/${y}" "${x}"
+  else
+    echo "'../../../src/${y}' doesn't exist, not copying"
+  fi
+done
 mvn package -Dgetdown.host.whitelist="jalview.org,*.jalview.org" -Dallow_file_protocol=false -Dconnect_timeout=8 -Dread_timeout=15
 RET=$?
 if [ x$RET = x0 ]; then
index 88464c3..746765e 100644 (file)
Binary files a/j11lib/getdown-core.jar and b/j11lib/getdown-core.jar differ
index 88464c3..746765e 100644 (file)
Binary files a/j8lib/getdown-core.jar and b/j8lib/getdown-core.jar differ
index 2a78d8e..9387be4 100644 (file)
 package jalview.bin;
 
 import java.io.File;
-import java.io.IOException;
 import java.lang.management.ManagementFactory;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
 
 import jalview.bin.argparser.Arg;
 import jalview.util.ChannelProperties;
+import jalview.util.ErrorLog;
 import jalview.util.LaunchUtils;
 
 /**
@@ -80,6 +78,8 @@ public class Launcher
                       + LaunchUtils.getJavaCompileVersion() + ".");
     }
 
+    ErrorLog.setPrefix("LAUNCHER - ");
+
     String jvmmempc = null;
     String jvmmemmax = null;
     boolean debug = false;
@@ -215,18 +215,14 @@ public class Launcher
     // if we're using jalview.bin.Launcher we always assume a console is in use
     final String javaBin = LaunchUtils.findJavaBin(true);
 
-    List<String> command = new ArrayList<>();
-    command.add(javaBin);
+    List<String> removeJvmArgs = new ArrayList<>();
+    List<String> addJvmArgs = new ArrayList<>();
+    // command.add(javaBin);
 
     String memSetting = null;
 
-    for (String jvmArg : ManagementFactory.getRuntimeMXBean()
-            .getInputArguments())
-    {
-      command.add(jvmArg);
-    }
-    command.add("-cp");
-    command.add(ManagementFactory.getRuntimeMXBean().getClassPath());
+    List<String> jvmArgs = ManagementFactory.getRuntimeMXBean()
+            .getInputArguments();
 
     // use saved preferences if no cmdline args
     boolean useCustomisedSettings = LaunchUtils
@@ -251,9 +247,9 @@ public class Launcher
     boolean dockName = false;
     boolean headlessProp = false;
     boolean macosHeadlessProp = false;
-    for (int i = 0; i < command.size(); i++)
+    for (int i = 0; i < jvmArgs.size(); i++)
     {
-      String arg = command.get(i);
+      String arg = jvmArgs.get(i);
       if (arg.startsWith("-Xmx"))
       {
         // only use -Xmx if jvmmemmax and jvmmempc have not been set
@@ -289,7 +285,8 @@ public class Launcher
       {
         memSetting = "-Xmx" + Long.toString(maxMemLong);
         memSet = true;
-        command.add(memSetting);
+        addJvmArgs.add(memSetting);
+        removeJvmArgs.add("-Xmx");
       }
     }
 
@@ -299,15 +296,15 @@ public class Launcher
       {
         String dockIconPath = System.getProperty("getdownappdir", ".")
                 + File.separator + "resource/jalview_logo.png";
-        command.add("-Xdock:icon=" + dockIconPath);
+        addJvmArgs.add("-Xdock:icon=" + dockIconPath);
       }
       if (!dockName)
       {
         // -Xdock:name=... doesn't actually work :(
         // Leaving it in in case it gets fixed
-        command.add("-Xdock:name=" + appName);
+        addJvmArgs.add("-Xdock:name=" + appName);
         // This also does not work for the dock
-        command.add("-Dcom.apple.mrj.application.apple.menu.about.name="
+        addJvmArgs.add("-Dcom.apple.mrj.application.apple.menu.about.name="
                 + appName);
       }
     }
@@ -317,109 +314,28 @@ public class Launcher
       /* not setting this in java invocation of running jalview due to problem with Jmol */
       if (help)
       {
-        command.add("-D" + headlessProperty + "=true");
+        addJvmArgs.add("-D" + headlessProperty + "=true");
       }
     }
     if (headless && LaunchUtils.isMac && !macosHeadlessProp)
     {
       System.setProperty(macosHeadlessProperty, "true");
-      command.add("-D" + macosHeadlessProperty + "=true");
+      addJvmArgs.add("-D" + macosHeadlessProperty + "=true");
     }
 
     String scalePropertyArg = HiDPISetting.getScalePropertyArg();
     if (scalePropertyArg != null)
     {
-      syserr(debug, quiet, "Running " + startClass + " with scale setting "
-              + scalePropertyArg);
-      command.add(scalePropertyArg);
+      LaunchUtils.syserr(debug, quiet, "Running " + startClass
+              + " with scale setting " + scalePropertyArg);
+      addJvmArgs.add(scalePropertyArg);
     }
 
-    command.add(startClass);
-    command.addAll(arguments);
+    int exitValue = LaunchUtils.startNewJvm(javaBin, removeJvmArgs,
+            addJvmArgs, null, null, null, startClass, null, null, arguments,
+            launcherprint, launcherwait, launcherstop, debug, quiet);
 
-    final ProcessBuilder builder = new ProcessBuilder(command);
-
-    if ((Boolean.parseBoolean(System.getProperty("launcherprint", "false"))
-            || launcherprint))
-    {
-      syserr(debug, quiet,
-              "LAUNCHER COMMAND: " + String.join(" ", builder.command()));
-    }
-    syserr(debug, quiet,
-            "Running " + startClass + " with "
-                    + (memSetting == null ? "no memory setting"
-                            : ("memory setting " + memSetting)));
-
-    if (Boolean.parseBoolean(System.getProperty("launcherstop", "false"))
-            || (debug && launcherstop))
-    {
-      syserr(debug, quiet,
-              "System property 'launcherstop' is set and not 'false'. Exiting.");
-      System.exit(0);
-    }
-    try
-    {
-      builder.inheritIO();
-      Process process = builder.start();
-      if (wait || launcherwait)
-      {
-        syserr(debug, quiet, "Launching application process");
-        process.waitFor();
-      }
-      else
-      {
-        int waitInt = 0;
-        syserr(debug, quiet,
-                "Wait time for application process is " + waitInt + "ms");
-        process.waitFor(waitInt, TimeUnit.MILLISECONDS);
-      }
-      syserr(debug, quiet, "Launcher process ending");
-    } catch (IOException e)
-    {
-      if (e.getMessage().toLowerCase(Locale.ROOT).contains("memory"))
-      {
-        jalview.bin.Console
-                .errPrintln("Caught a memory exception: " + e.getMessage());
-        // Probably the "Cannot allocate memory" error, try without the memory
-        // setting
-        ArrayList<String> commandNoMem = new ArrayList<>();
-        for (int i = 0; i < command.size(); i++)
-        {
-          if (!command.get(i).startsWith("-Xmx"))
-          {
-            commandNoMem.add(command.get(i));
-          }
-        }
-        final ProcessBuilder builderNoMem = new ProcessBuilder(
-                commandNoMem);
-        jalview.bin.Console.errPrintln("Command without memory setting: "
-                + String.join(" ", builderNoMem.command()));
-        try
-        {
-          builderNoMem.inheritIO();
-          Process processNoMem = builderNoMem.start();
-          processNoMem.waitFor();
-        } catch (Exception ex)
-        {
-          ex.printStackTrace();
-        }
-      }
-      else
-      {
-        e.printStackTrace();
-      }
-    } catch (Exception e)
-    {
-      e.printStackTrace();
-    }
-  }
-
-  private static void syserr(boolean debug, boolean quiet, String message)
-  {
-    if (debug && !quiet)
-    {
-      jalview.bin.Console.errPrintln("LAUNCHERDEBUG - " + message);
-    }
+    LaunchUtils.syserr(debug, quiet, "JVM exited with value " + exitValue);
   }
 
 }
index e94b59e..bbc758b 100644 (file)
@@ -1,9 +1,32 @@
 package jalview.util;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 public class ErrorLog
 {
   private static boolean hasConsole = true;
 
+  private static Class console = null;
+
+  private static Method initLogger = null;
+
+  private static Method errPrintln = null;
+
+  private static Method outPrintln = null;
+
+  private static String prefix = null;
+
+  public static void setHasConsole(boolean b)
+  {
+    hasConsole = b;
+  }
+
+  public static void setPrefix(String s)
+  {
+    prefix = s;
+  }
+
   public static void outPrintln(String message)
   {
     println(message, false);
@@ -16,22 +39,64 @@ public class ErrorLog
 
   public static void println(String message, boolean err)
   {
-    if (hasConsole)
+    println(message, err, true);
+  }
+
+  public static void println(String message0, boolean err,
+          boolean thisHasConsole)
+  {
+    String message = prefix == null ? message0 : prefix + message0;
+    if (thisHasConsole && hasConsole)
     {
       try
       {
-        hasConsole = jalview.bin.Console.initLogger();
-        if (hasConsole)
+        if (console == null)
+        {
+          Class console = Class.forName("jalview.bin.Console");
+        }
+        if (console == null)
+        {
+          hasConsole = false;
+        }
+        else
         {
-          if (err)
+          if (initLogger == null && console != null)
           {
-            jalview.bin.Console.errPrintln(message);
+            initLogger = console.getMethod("initLogger");
           }
-          else
+          hasConsole = console == null || initLogger == null
+                  || (Boolean) initLogger.invoke(null);
+          if (hasConsole && console != null)
           {
-            jalview.bin.Console.outPrintln(message);
+            if (err)
+            {
+              if (errPrintln == null)
+              {
+                errPrintln = console.getMethod("errPrintln", String.class);
+              }
+              errPrintln.invoke(null, message);
+            }
+            else
+            {
+              if (outPrintln == null)
+              {
+                outPrintln = console.getMethod("outPrintln", String.class);
+              }
+              outPrintln.invoke(null, message);
+            }
           }
         }
+      } catch (ClassNotFoundException | NoSuchMethodException e)
+      {
+        hasConsole = false;
+        System.err.println(
+                "jalview.util.ErrorLog has no jalview.bin.Console.initLogger(). Using System.err and System.out.");
+      } catch (IllegalAccessException | IllegalArgumentException
+              | InvocationTargetException e)
+      {
+        hasConsole = false;
+        System.err.println(
+                "jalview.util.ErrorLog had a problem calling a method of jalview.bin.Console.  Using System.err and System.out.");
       } catch (Exception e)
       {
         e.printStackTrace();
@@ -42,7 +107,7 @@ public class ErrorLog
                 "jalview.util.ErrorLog has no jalview.bin.Console. Using System.err and System.out.");
       }
     }
-    if (!hasConsole)
+    if (!(thisHasConsole && hasConsole))
     {
       if (err)
       {
index f8cc269..66c983c 100644 (file)
@@ -25,9 +25,18 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.management.ManagementFactory;
 import java.net.MalformedURLException;
+import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
 import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 public class LaunchUtils
 {
@@ -112,7 +121,8 @@ public class LaunchUtils
               null);
       if (JCV == null)
       {
-        ErrorLog.errPrintln("Could not obtain JAVA_COMPILE_VERSION for comparison");
+        ErrorLog.errPrintln(
+                "Could not obtain JAVA_COMPILE_VERSION for comparison");
         return -2;
       }
       JAVA_COMPILE_VERSION = Integer.parseInt(JCV);
@@ -167,6 +177,19 @@ public class LaunchUtils
     return JAVA_VERSION;
   }
 
+  public static String getJarPath(Class c)
+  {
+    try
+    {
+      return c.getProtectionDomain().getCodeSource().getLocation().toURI()
+              .getPath();
+    } catch (URISyntaxException e)
+    {
+      ErrorLog.errPrintln("Problem with class source location");
+      return null;
+    }
+  }
+
   public static boolean checkJavaVersion()
   {
     if (LaunchUtils.isJS)
@@ -197,20 +220,29 @@ public class LaunchUtils
 
   public static String findJavaBin(boolean winConsole)
   {
-    return findJavaBin(System.getProperty("java.home"), winConsole, true);
+    return findJavaBin(System.getProperty("java.home"), winConsole, true,
+            true);
+  }
+
+  public static String findJavaBin(boolean winConsole,
+          boolean applicationName, boolean generic)
+  {
+    return findJavaBin(System.getProperty("java.home"), winConsole,
+            applicationName, generic);
   }
 
   /*
    * Returns a string path to the most likely java binary wanted to run this
    * installation of Jalview.
    * 
+   * @param  javaHome    Try this javaHome dir (defaults to the running java.home).
    * @param  winConsole  whether to use java.exe (console) in preference to javaw.exe
    *                     (only affects Windows).
-   * @param  javaHome    Try this javaHome dir (defaults to the running java.home).
+   * @param  applicationName  Look to see if the Jalview application name symbolic link is present and use it.
    * @param  generic     Return a generic java command if not found.
    */
   public static String findJavaBin(String javaHome, boolean winConsole,
-          boolean generic)
+          boolean applicationName, boolean generic)
   {
     String javaBin = null;
     final String javaExe = winConsole ? "java.exe" : "javaw.exe";
@@ -218,11 +250,16 @@ public class LaunchUtils
 
     if (javaHome != null)
     {
+      String propertyAppName = null;
+      String appName = null;
       // property "channel.app_name" is set by install4j when launching getdown
-      String propertyAppName = System.getProperty("channel.app_name");
-      final String appName = (propertyAppName != null
-              && propertyAppName.length() > 0) ? propertyAppName
-                      : ChannelProperties.getProperty("app_name");
+      if (applicationName)
+      {
+        propertyAppName = System.getProperty("channel.app_name");
+        appName = (propertyAppName != null && propertyAppName.length() > 0)
+                ? propertyAppName
+                : ChannelProperties.getProperty("app_name");
+      }
 
       final String javaBinDir = javaHome + File.separator + "bin"
               + File.separator;
@@ -230,8 +267,26 @@ public class LaunchUtils
       // appName and "Jalview" will not point to javaw.exe or java.exe but in
       // this case that's okay because the taskbar display name problem doesn't
       // manifest in Windows. See JAL-3820, JAL-4189.
-      for (String name : new String[] { appName, ChannelProperties.FALLBACK_APPNAME, java, javaExe })
+      List<String> potentialJavaBin = new ArrayList<>();
+      if (applicationName)
       {
+        if (appName != null)
+        {
+          potentialJavaBin.add(appName);
+        }
+        if (ChannelProperties.FALLBACK_APPNAME != null)
+        {
+          potentialJavaBin.add(ChannelProperties.FALLBACK_APPNAME);
+        }
+      }
+      potentialJavaBin.add(java);
+      potentialJavaBin.add(javaExe);
+      for (String name : potentialJavaBin)
+      {
+        if (name == null)
+        {
+          continue;
+        }
         if (LaunchUtils.checkJVMSymlink(javaBinDir + name, winConsole))
         {
           javaBin = javaBinDir + name;
@@ -278,4 +333,260 @@ public class LaunchUtils
     }
     return false;
   }
+
+  /**
+   * Create a java command that matches the currently running java process and
+   * optionally remove/add some JVM and application parameters.
+   * 
+   * @param String
+   *          javaBinary The java binary to use. null uses the same as current
+   *          process.
+   * @param String[]
+   *          removeJvmArguments The (start of) JVM arguments to remove.
+   * @param String[]
+   *          addJvmArguments JVM arguments to add.
+   * @param String[]
+   *          prependToClasspath Add these dirs to the start of the classpath
+   * @param String[]
+   *          appendToClasspath Add these dirs to the end of the classpath
+   * @param String[]
+   *          deleteFromClasspath Remove these dirs from the existing classpath
+   * @param String
+   *          startClass The name of the start class if different. null if the
+   *          same.
+   * @param String[]
+   *          removeAppArguments The (start of) application arguments to remove.
+   * @param String[]
+   *          addAppArguments Application arguments to add.
+   * @param boolean
+   *          terminate Flag to terminate this process after starting new
+   *          process.
+   */
+  public static int startNewJvm(String javaBinary,
+          List<String> removeJvmArguments, List<String> addJvmArguments,
+          List<String> prependToClasspath, List<String> appendToClasspath,
+          List<String> removeFromClasspath, String startClass,
+          List<String> removeAppArguments, List<String> addAppArguments,
+          List<String> appArguments, boolean launcherprint,
+          boolean launcherwait, boolean launcherstop, boolean debug,
+          boolean quiet)
+  {
+    int exitValue = -1;
+    if (javaBinary == null)
+    {
+      javaBinary = findJavaBin(false, true, true);
+    }
+
+    List<String> classpathDirs = new ArrayList<>();
+    if (prependToClasspath != null)
+    {
+      classpathDirs.addAll(prependToClasspath);
+    }
+
+    String classpath = ManagementFactory.getRuntimeMXBean().getClassPath();
+    if (removeFromClasspath != null)
+    {
+      Set<String> removeCp = new HashSet<>();
+      for (String dcp : removeFromClasspath)
+      {
+        try
+        {
+          String canPath = new File(dcp).getCanonicalPath();
+          removeCp.add(canPath);
+        } catch (IOException e)
+        {
+          ErrorLog.errPrintln(
+                  "Problem getting canonical path. " + e.getMessage());
+        }
+      }
+      for (String cp : classpath.split(File.pathSeparator))
+      {
+        try
+        {
+          String canPath = new File(cp).getCanonicalPath();
+          if (!removeCp.contains(canPath))
+          {
+            classpathDirs.add(cp);
+          }
+        } catch (IOException e)
+        {
+          ErrorLog.errPrintln(
+                  "Problem getting canonical path. " + e.getMessage());
+        }
+      }
+    }
+    else
+    {
+      classpathDirs
+              .addAll(Arrays.asList(classpath.split(File.pathSeparator)));
+    }
+    if (appendToClasspath != null)
+    {
+      classpathDirs.addAll(appendToClasspath);
+    }
+
+    List<String> jvmArguments = new ArrayList<>();
+    List<String> originalJvmArguments = ManagementFactory.getRuntimeMXBean()
+            .getInputArguments();
+    if (removeJvmArguments != null)
+    {
+      for (String jvmArg : originalJvmArguments)
+      {
+        boolean addArg = true;
+        for (String rmArg : removeJvmArguments)
+        {
+          if (jvmArg.startsWith(rmArg))
+          {
+            addArg = false;
+            break;
+          }
+        }
+        if (addArg)
+        {
+          jvmArguments.add(jvmArg);
+        }
+      }
+    }
+    else
+    {
+      jvmArguments.addAll(originalJvmArguments);
+    }
+    if (addJvmArguments != null)
+    {
+      jvmArguments.addAll(addJvmArguments);
+    }
+
+    if (startClass == null)
+    {
+      // this isn't always reliable
+      startClass = System.getProperty("sun.java.command");
+    }
+
+    List<String> applicationArguments = new ArrayList<>();
+    if (removeAppArguments != null)
+    {
+      Set<String> removeArgs = new HashSet<>(removeAppArguments);
+      for (String appArg : appArguments)
+      {
+        if (!removeArgs.contains(removeArgs))
+        {
+          applicationArguments.add(appArg);
+        }
+      }
+    }
+    else
+    {
+      applicationArguments.addAll(appArguments);
+    }
+    if (addAppArguments != null)
+    {
+      applicationArguments.addAll(addAppArguments);
+    }
+
+    List<String> command = new ArrayList<>();
+    // java command
+    command.add(javaBinary);
+
+    // classpath
+    command.add("-cp");
+    command.add(String.join(File.pathSeparator, classpathDirs));
+
+    // jvm args
+    command.addAll(jvmArguments);
+
+    // start class
+    command.add(startClass);
+
+    // application args
+    command.addAll(applicationArguments);
+
+    final ProcessBuilder builder = new ProcessBuilder(command);
+
+    if (Boolean.parseBoolean(System.getProperty("launcherprint", "false"))
+            || launcherprint)
+    {
+      syserr(debug, quiet,
+              "COMMAND: " + String.join(" ", builder.command()));
+    }
+
+    if (Boolean.parseBoolean(System.getProperty("launcherstop", "false"))
+            || (debug && launcherstop))
+    {
+      syserr(debug, quiet,
+              "System property 'launcherstop' is set and not 'false'. Exiting.");
+      System.exit(0);
+    }
+    try
+    {
+      builder.inheritIO();
+      Process process = builder.start();
+      if (launcherwait)
+      {
+        syserr(debug, quiet, "Launching application process");
+        exitValue = process.waitFor();
+        syserr(debug, quiet,
+                "Application process return with value " + exitValue);
+      }
+      else
+      {
+        int waitInt = 0;
+        syserr(debug, quiet,
+                "Wait time for application process is " + waitInt + "ms");
+        if (process.waitFor(waitInt, TimeUnit.MILLISECONDS))
+        {
+          exitValue = process.exitValue();
+        }
+        else
+        {
+          exitValue = -2;
+        }
+      }
+      syserr(debug, quiet, "Launcher process ending");
+    } catch (IOException e)
+    {
+      if (e.getMessage().toLowerCase(Locale.ROOT).contains("memory"))
+      {
+        syserr(true, quiet, "Caught a memory exception: " + e.getMessage());
+        // Probably the "Cannot allocate memory" error, try without the memory
+        // setting
+        ArrayList<String> commandNoMem = new ArrayList<>();
+        for (int i = 0; i < command.size(); i++)
+        {
+          if (!command.get(i).startsWith("-Xmx"))
+          {
+            commandNoMem.add(command.get(i));
+          }
+        }
+        final ProcessBuilder builderNoMem = new ProcessBuilder(
+                commandNoMem);
+        syserr(true, quiet, "Command without memory setting: "
+                + String.join(" ", builderNoMem.command()));
+        try
+        {
+          builderNoMem.inheritIO();
+          Process processNoMem = builderNoMem.start();
+          exitValue = processNoMem.waitFor();
+        } catch (Exception ex)
+        {
+          ex.printStackTrace();
+        }
+      }
+      else
+      {
+        e.printStackTrace();
+      }
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+    return exitValue;
+  }
+
+  public static void syserr(boolean debug, boolean quiet, String message)
+  {
+    if (debug && !quiet)
+    {
+      ErrorLog.errPrintln("DEBUG - " + message);
+    }
+  }
 }
index 44421c2..e019562 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<install4j version="10.0.8" transformSequenceNumber="10">
+<install4j version="10.0.7" transformSequenceNumber="10">
   <directoryPresets config="bin/Jalview" />
   <application name="${compiler:JALVIEW_APPLICATION_NAME}" applicationId="${compiler:WINDOWS_APPLICATION_ID}" mediaDir="${compiler:BUILD_DIR}" lzmaCompression="true" shortName="${compiler:INTERNAL_ID}" publisher="University of Dundee" publisherWeb="https://www.jalview.org/" version="${compiler:JALVIEW_VERSION}" allPathsRelative="true" macVolumeId="5aac4968c304f65" javaMinVersion="${compiler:JAVA_MIN_VERSION}" javaMaxVersion="${compiler:JAVA_MAX_VERSION}" allowBetaVM="true" jdkMode="jdk" jdkName="JDK 11.0">
     <searchSequence>
   </files>
   <launchers>
     <launcher name="Jalview User Launcher" id="2823" customizedId="JALVIEW" menuName="${compiler:JALVIEW_APPLICATION_NAME}" icnsFile="${compiler:JALVIEW_DIR}/${compiler:MAC_ICONS_FILE}" customMacBundleIdentifier="true" macBundleIdentifier="${compiler:BUNDLE_ID}" fileset="734" addMacApplicationCategory="true" macApplicationCategory="public.app-category.education" useCustomMacosExecutableName="true" customMacosExecutableName="${compiler:JALVIEW_APPLICATION_NAME}">
-      <executable name="${compiler:EXECUTABLE_NAME}" iconSet="true" iconFile="${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE}" stderrFile="~/.${compiler:UNIX_APPLICATION_FOLDER}-launcher.log" redirectStdout="true" stdoutFile="~/${compiler:UNIX_APPLICATION_FOLDER}-launcher.log" executableMode="gui" changeWorkingDirectory="false" singleInstance="true" executionLevel="highestAvailable" checkConsoleParameter="true">
+      <executable name="${compiler:EXECUTABLE_NAME}" iconSet="true" iconFile="${compiler:JALVIEW_DIR}/${compiler:WINDOWS_ICONS_FILE}" stderrFile="~/.${compiler:UNIX_APPLICATION_FOLDER}-launcher.log" redirectStdout="true" stdoutFile="~/.${compiler:UNIX_APPLICATION_FOLDER}-launcher.log" executableMode="gui" changeWorkingDirectory="false" singleInstance="true" executionLevel="highestAvailable" checkConsoleParameter="true">
         <versionInfo include="true" fileDescription="${compiler:sys.fullName}" legalCopyright="${compiler:COPYRIGHT_MESSAGE}" internalName="${compiler:INTERNAL_ID}" productName="${compiler:sys.fullName}" />
       </executable>
       <splashScreen width="640" height="480" bitmapFile="${compiler:JALVIEW_DIR}/${compiler:BACKGROUND}" textOverlay="true">
           <versionLine x="85" y="109" text="version ${compiler:sys.version}" />
         </text>
       </splashScreen>
-      <java mainClass="com.threerings.getdown.launcher.GetdownApp" vmParameters="-Dusedefaultappdir=true -Dpopulatedefaultappdir=true -Dappid=jalview -Dinstaller.template_version=${compiler:INSTALLER_TEMPLATE_VERSION} -Dinstaller.appdir=&quot;${launcher:sys.launcherDirectory}&quot; -Dinstaller.application_folder=&quot;${compiler:APPLICATION_FOLDER}&quot; -Dchannel.app_name=&quot;${compiler:JALVIEW_APPLICATION_NAME}&quot;">
+      <java mainClass="com.threerings.getdown.launcher.GetdownApp" vmParameters="-Duserdefaultappdir=true -Dpopulatedefaultappdir=true -Dappid=jalview -Dinstaller.template_version=${compiler:INSTALLER_TEMPLATE_VERSION} -Dinstaller.appdir=&quot;${launcher:sys.launcherDirectory}&quot; -Dinstaller.application_folder=&quot;${compiler:APPLICATION_FOLDER}&quot; -Dchannel.app_name=&quot;${compiler:JALVIEW_APPLICATION_NAME}&quot;">
         <classPath>
           <archive location="getdown-launcher.jar" failOnError="false" />
           <archive location="${compiler:GETDOWN_INSTALL_DIR}/getdown-launcher.jar" failOnError="false" />
 # -include-options [path to other .vmoption file]
 
 # Uncomment these two lines to disable user-space automatic updates.
-#-Dnousedefaultappdir=true
+#-Dnouserdefaultappdir=true
 #-Dsilent=noupdate</content>
       </vmOptionsFile>
       <infoPlist>${compiler:file("${compiler:INFO_PLIST_FILE_ASSOCIATIONS_FILE}")}</infoPlist>