/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.util; import java.io.File; 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; import java.util.jar.Attributes; import java.util.jar.JarInputStream; import java.util.jar.Manifest; public class LaunchUtils { // setting these is LaunchUtils so don't need to import Platform public final static boolean isMac = System.getProperty("os.name") .indexOf("Mac") > -1; public final static boolean isWindows = System.getProperty("os.name") .indexOf("Win") > -1; private static boolean isJS = /** @j2sNative true || */ false; public static final String LOGFILE_HANDOVER = "LOGFILE_HANDOVER"; public static void loadChannelProps(File dir) { ChannelProperties.loadProps(dir); } private static Properties userPreferences = null; public static String getUserPreference(String key) { if (userPreferences == null) { String channelPrefsFilename = ChannelProperties .getProperty("preferences.filename"); if (channelPrefsFilename == null) { return null; } File propertiesFile = new File(System.getProperty("user.home"), channelPrefsFilename); if (!propertiesFile.exists()) { return null; } try { userPreferences = new Properties(); userPreferences.load(new FileInputStream(propertiesFile)); } catch (FileNotFoundException e) { // didn't find user preferences file return null; } catch (IOException e) { ErrorLog.errPrintln(e.getMessage()); return null; } } return userPreferences.getProperty(key); } public static boolean getBooleanUserPreference(String key) { return Boolean.parseBoolean(getUserPreference(key)); } public static int JAVA_COMPILE_VERSION = 0; public static int getJavaCompileVersion() { if (LaunchUtils.isJS) { return -1; } else if (JAVA_COMPILE_VERSION > 0) { return JAVA_COMPILE_VERSION; } String buildDetails = "jar:".concat(LaunchUtils.class .getProtectionDomain().getCodeSource().getLocation().toString() .concat("!" + "/.build_properties")); try { URL localFileURL = new URL(buildDetails); InputStream in = HttpUtils.openStream(localFileURL); Properties buildProperties = new Properties(); buildProperties.load(in); in.close(); String JCV = buildProperties.getProperty("JAVA_COMPILE_VERSION", null); if (JCV == null) { ErrorLog.errPrintln( "Could not obtain JAVA_COMPILE_VERSION for comparison"); return -2; } JAVA_COMPILE_VERSION = Integer.parseInt(JCV); } catch (MalformedURLException e) { ErrorLog.errPrintln("Could not find " + buildDetails); return -3; } catch (IOException e) { ErrorLog.errPrintln("Could not load " + buildDetails); return -4; } catch (NumberFormatException e) { ErrorLog.errPrintln("Could not parse JAVA_COMPILE_VERSION"); return -5; } return JAVA_COMPILE_VERSION; } public static int JAVA_VERSION = 0; public static int getJavaVersion() { if (LaunchUtils.isJS) { return -1; } else if (JAVA_VERSION > 0) { return JAVA_VERSION; } try { String JV = System.getProperty("java.version"); if (JV == null) { ErrorLog.errPrintln("Could not obtain java.version for comparison"); return -2; } if (JV.startsWith("1.")) { JV = JV.substring(2); } JAVA_VERSION = JV.indexOf(".") == -1 ? Integer.parseInt(JV) : Integer.parseInt(JV.substring(0, JV.indexOf("."))); } catch (NumberFormatException e) { ErrorLog.errPrintln("Could not parse java.version"); return -3; } 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) { return true; } String buildDetails = "jar:".concat(LaunchUtils.class .getProtectionDomain().getCodeSource().getLocation().toString() .concat("!" + "/.build_properties")); int java_compile_version = getJavaCompileVersion(); int java_version = getJavaVersion(); if (java_compile_version <= 0 || java_version <= 0) { ErrorLog.errPrintln("Could not make Java version check"); return true; } // Warn if these java.version and JAVA_COMPILE_VERSION conditions exist // Usually this means a Java 11 compiled JAR being run by a Java 11 JVM if (java_version >= 11 && java_compile_version < 11) { return false; } return true; } public static String findJavaBin(boolean winConsole) { 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 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 applicationName, boolean generic) { String javaBin = null; final String javaExe = winConsole ? "java.exe" : "javaw.exe"; final String java = "java"; if (javaHome != null) { String propertyAppName = null; String appName = null; // property "channel.app_name" is set by install4j when launching getdown 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; // 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. List 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; break; } } } if (javaBin == null && generic) { javaBin = LaunchUtils.isWindows ? javaExe : java; } return javaBin; } /* * checkJVMSymlink returns true if the path in testBin *is* a java binary, or * points to a java binary. * @param testBin The binary or symbolic link to check * @param winConsole whether we are in/want a Windows console (only relevant for Windows, * determines whether we use java.exe or javaw.exe) */ private static boolean checkJVMSymlink(String testBin, boolean winConsole) { File testBinFile = new File(testBin); if (!testBinFile.exists()) { return false; } File targetFile = null; try { targetFile = testBinFile.getCanonicalFile(); } catch (IOException e) { return false; } final String javaExe = winConsole ? "java.exe" : "javaw.exe"; if (targetFile != null && ("java".equals(targetFile.getName()) || javaExe.equals(targetFile.getName()))) { return true; } 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 removeJvmArguments, List addJvmArguments, List prependToClasspath, List appendToClasspath, List removeFromClasspath, String startClass, List removeAppArguments, List addAppArguments, List appArguments, boolean launcherprint, boolean launcherwait, boolean launcherstop, boolean debug, boolean quiet) { if (javaBinary == null) { javaBinary = findJavaBin(false, true, true); } List classpathDirs = new ArrayList<>(); if (prependToClasspath != null) { classpathDirs.addAll(prependToClasspath); } String classpath = ManagementFactory.getRuntimeMXBean().getClassPath(); if (removeFromClasspath != null) { Set 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 jvmArguments = new ArrayList<>(); List 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 applicationArguments = new ArrayList<>(); if (removeAppArguments != null) { Set 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 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); return runProcess(command, launcherprint, launcherwait, launcherstop, debug, quiet); } private static int runProcess(List command, boolean launcherprint, boolean launcherwait, boolean launcherstop, boolean debug, boolean quiet) { final ProcessBuilder builder = new ProcessBuilder(command); int exitValue = -1; 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 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; } /** * Look for Implementation-Version in two jar manifests and compare according * to the getdown-launcher version spec (1.8.3-1.4.0_JVL or _FJVL) returns -1 * if f0 is newer (or more "valid"), +1 if f1 is newer (or more "valid"), 0 if * the same or both equally "invalid" * * @param f0 * @param f1 * @return int */ public static int compareGetdownLauncherJarVersions(File f0, File f1) { if (!f0.exists() && !f1.exists()) { return 0; } if (!f0.exists()) { return -1; } if (!f1.exists()) { return 1; } String v0 = getJarImplementationVersion(f0); String v1 = getJarImplementationVersion(f1); syserr(v0 != null && !v0.equals(v1), false, "Got launcher versions '" + v0 + "' and '" + v1 + "'"); return compareGetdownLauncherJarVersions(v0, v1); } public static int compareGetdownLauncherJarVersions(String v0, String v1) { if (v0 == null && v1 == null) { return 0; } if (v0 == null) { return -1; } if (v1 == null) { return 1; } // remove the subscript if (v0.endsWith("JVL")) { v0 = v0.substring(0, v0.lastIndexOf('_')); } if (v1.endsWith("JVL")) { v1 = v1.substring(0, v1.lastIndexOf('_')); } String[] v0parts = v0.split("-"); String[] v1parts = v1.split("-"); int compare = 0; for (int j = 0; j < Math.min(v0parts.length, v1parts.length); j++) { compare = compareVersions(v0parts[j], v1parts[j]); if (compare != 0) { return compare; } } return v0parts.length - v1parts.length; } /** * comparing versions numbers of the form 1.2.3.4...n ONLY returns 0 if v0 and * v1 are the same, a negative number if v0 < v1 and a positive number if v0 > * v1. The number returned does NOT show how far apart the version numbers * are. */ public static int compareVersions(String v0, String v1) { if (v0 == null && v1 == null) { return 0; } if (v0 == null) { return -1; } if (v1 == null) { return 1; } String[] v0dots = v0.split("\\."); String[] v1dots = v1.split("\\."); int compare = 0; for (int i = 0; i < Math.min(v0dots.length, v1dots.length); i++) { if (!v0dots[i].equals(v1dots[i])) // avoids unnecessary // NumberFormatException { try { compare = Integer.parseInt(v0dots[i]) - Integer.parseInt(v1dots[i]); } catch (NumberFormatException e) { syserr(true, false, "Couldn't parse one of '" + v0dots[i] + "' or '" + v1dots[i] + "': " + e.getMessage()); syserr(true, false, "Comparing as strings."); compare = v0dots[i].compareTo(v1dots[i]); } if (compare != 0) { return compare; } } } // all numbers match up to min length. If one has more dots, assume it's // a greater version (e.g. 1.3.2 > 1.3) return v0dots.length - v1dots.length; } public static String getJarImplementationVersion(File jarFile) { String implementationVersion = null; try { JarInputStream j0 = new JarInputStream(new FileInputStream(jarFile)); Manifest m0 = j0.getManifest(); if (m0 == null) { System.err.println("No manifest in " + jarFile.getAbsolutePath()); } else { implementationVersion = m0.getMainAttributes() .getValue(Attributes.Name.IMPLEMENTATION_VERSION); } } catch (IOException e) { System.err.println("Exception opening " + jarFile.getAbsolutePath() + " to check version: " + e.getMessage()); } return implementationVersion; } public static void syserr(boolean debug, boolean quiet, String message) { if (debug && !quiet) { ErrorLog.errPrintln("DEBUG - " + message); } } }