Copyright (c) 2000, 2019 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2000, 2019 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/
package org.eclipse.jdt.internal.launching; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.IProcess; import org.eclipse.jdt.launching.AbstractVMRunner; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.IVMInstall; import org.eclipse.jdt.launching.IVMInstall2; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.VMRunnerConfiguration; import org.eclipse.osgi.util.NLS; import com.ibm.icu.text.DateFormat;
A launcher for running Java main classes.
/** * A launcher for running Java main classes. */
public class StandardVMRunner extends AbstractVMRunner {
Constant representing the -XstartOnFirstThread VM argument
Since:3.2.200
/** * Constant representing the <code>-XstartOnFirstThread</code> VM argument * * @since 3.2.200 */
public static final String XSTART_ON_FIRST_THREAD = "-XstartOnFirstThread"; //$NON-NLS-1$
The VM install instance
/** * The VM install instance */
protected IVMInstall fVMInstance;
Constructor
Params:
  • vmInstance – the VM
/** * Constructor * @param vmInstance the VM */
public StandardVMRunner(IVMInstall vmInstance) { fVMInstance= vmInstance; }
Returns the 'rendered' name for the current target
Params:
  • classToRun – the class
  • host – the host name
Returns:the name for the current target
/** * Returns the 'rendered' name for the current target * @param classToRun the class * @param host the host name * @return the name for the current target */
protected String renderDebugTarget(String classToRun, int host) { String format= LaunchingMessages.StandardVMRunner__0__at_localhost__1__1; return NLS.bind(format, new String[] { classToRun, String.valueOf(host) }); }
Returns the 'rendered' name for the specified command line
Params:
  • commandLine – the command line
  • timestamp – the run-at time for the process
Returns:the name for the process
/** * Returns the 'rendered' name for the specified command line * @param commandLine the command line * @param timestamp the run-at time for the process * @return the name for the process */
public static String renderProcessLabel(String[] commandLine, String timestamp) { String format= LaunchingMessages.StandardVMRunner__0____1___2; return NLS.bind(format, new String[] { commandLine[0], timestamp }); }
Prepares the command line from the specified array of strings
Params:
  • commandLine – the command line
Returns:the command line label
/** * Prepares the command line from the specified array of strings * @param commandLine the command line * @return the command line label */
protected String renderCommandLine(String[] commandLine) { return DebugPlugin.renderArguments(commandLine, null); }
Adds the array of Strings to the given List
Params:
  • args – the strings
  • v – the list
/** * Adds the array of {@link String}s to the given {@link List} * @param args the strings * @param v the list */
protected void addArguments(String[] args, List<String> v) { if (args == null) { return; } for (int i= 0; i < args.length; i++) { v.add(args[i]); } }
This method allows consumers to have a last look at the command line that will be used to start the runner just prior to launching. This method returns the new array of commands to use to start the runner with or null if the existing command line should be used.

By default this method returns null indicating that the existing command line should be used to launch
Params:
Returns:the new command line to launch with or null if the existing one should be used
Since:3.7.0
/** * This method allows consumers to have a last look at the command line that will be used * to start the runner just prior to launching. This method returns the new array of commands * to use to start the runner with or <code>null</code> if the existing command line should be used. * <br><br> * By default this method returns <code>null</code> indicating that the existing command line should be used to launch * * @param configuration the backing {@link ILaunchConfiguration} * @param cmdLine the existing command line * @return the new command line to launch with or <code>null</code> if the existing one should be used * @since 3.7.0 */
protected String[] validateCommandLine(ILaunchConfiguration configuration, String[] cmdLine) { try { return wrap(configuration, cmdLine); } catch(CoreException ce) { LaunchingPlugin.log(ce); } return cmdLine; }
Adds in special command line arguments if SWT or the -ws directive are used
Params:
Throws:
Returns:the (possibly) modified command line to launch with
/** * Adds in special command line arguments if SWT or the <code>-ws</code> directive * are used * * @param config the backing {@link ILaunchConfiguration} * @param cmdLine the original VM arguments * @return the (possibly) modified command line to launch with * @throws CoreException */
private String[] wrap(ILaunchConfiguration config, String[] cmdLine) throws CoreException { if(config != null && Platform.OS_MACOSX.equals(Platform.getOS())) { for (int i= 0; i < cmdLine.length; i++) { if ("-ws".equals(cmdLine[i]) || cmdLine[i].indexOf("swt.jar") > -1 || cmdLine[i].indexOf("org.eclipse.swt") > -1) { //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ return createSWTlauncher(cmdLine, cmdLine[0], config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_START_ON_FIRST_THREAD, true)); } } } return cmdLine; }
Returns path to executable.
Params:
  • cmdLine – the old command line
  • vmVersion – the version of the VM
  • startonfirstthread –
Returns:the new command line
/** * Returns path to executable. * @param cmdLine the old command line * @param vmVersion the version of the VM * @param startonfirstthread * @return the new command line */
private String[] createSWTlauncher(String[] cmdLine, String vmVersion, boolean startonfirstthread) { // the following property is defined if Eclipse is started via java_swt String java_swt= System.getProperty("org.eclipse.swtlauncher"); //$NON-NLS-1$ if (java_swt == null) { // not started via java_swt -> now we require that the VM supports the "-XstartOnFirstThread" option boolean found = false; ArrayList<String> args = new ArrayList<>(); for (int i = 0; i < cmdLine.length; i++) { if(XSTART_ON_FIRST_THREAD.equals(cmdLine[i])) { found = true; } args.add(cmdLine[i]); } //newer VMs and non-MacOSX VMs don't like "-XstartOnFirstThread" // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=211625 if(!found && startonfirstthread) { //add it as the first VM argument args.add(1, XSTART_ON_FIRST_THREAD); } return args.toArray(new String[args.size()]); } try { // copy java_swt to /tmp in order to get the app name right Process process= Runtime.getRuntime().exec(new String[] { "/bin/cp", java_swt, "/tmp" }); //$NON-NLS-1$ //$NON-NLS-2$ process.waitFor(); java_swt= "/tmp/java_swt"; //$NON-NLS-1$ } catch (IOException e) { // ignore and run java_swt in place } catch (InterruptedException e) { // ignore and run java_swt in place } String[] newCmdLine= new String[cmdLine.length+1]; int argCount= 0; newCmdLine[argCount++]= java_swt; newCmdLine[argCount++]= "-XXvm=" + vmVersion; //$NON-NLS-1$ for (int i= 1; i < cmdLine.length; i++) { newCmdLine[argCount++]= cmdLine[i]; } return newCmdLine; }
Returns the working directory to use for the launched VM, or null if the working directory is to be inherited from the current process.
Params:
  • config – the VM configuration
Throws:
  • CoreException – if the working directory specified by the configuration does not exist or is not a directory
Returns:the working directory to use
/** * Returns the working directory to use for the launched VM, * or <code>null</code> if the working directory is to be inherited * from the current process. * * @param config the VM configuration * @return the working directory to use * @exception CoreException if the working directory specified by * the configuration does not exist or is not a directory */
protected File getWorkingDir(VMRunnerConfiguration config) throws CoreException { String path = config.getWorkingDirectory(); if (path == null) { return null; } File dir = new File(path); if (!dir.isDirectory()) { abort(NLS.bind(LaunchingMessages.StandardVMRunner_Specified_working_directory_does_not_exist_or_is_not_a_directory___0__3, new String[] {path}), null, IJavaLaunchConfigurationConstants.ERR_WORKING_DIRECTORY_DOES_NOT_EXIST); } return dir; } /* (non-Javadoc) * @see org.eclipse.jdt.launching.AbstractVMRunner#getPluginIdentifier() */ @Override protected String getPluginIdentifier() { return LaunchingPlugin.getUniqueIdentifier(); }
Construct and return a String containing the full path of a java executable command such as 'java' or 'javaw.exe'. If the configuration specifies an explicit executable, that is used.
Params:
  • config – the runner configuration
Throws:
Returns:full path to java executable
/** * Construct and return a String containing the full path of a java executable * command such as 'java' or 'javaw.exe'. If the configuration specifies an * explicit executable, that is used. * * @param config the runner configuration * @return full path to java executable * @exception CoreException if unable to locate an executable */
protected String constructProgramString(VMRunnerConfiguration config) throws CoreException { // Look for the user-specified java executable command String command= null; Map<String, Object> map= config.getVMSpecificAttributesMap(); if (map != null) { command = (String) map.get(IJavaLaunchConfigurationConstants.ATTR_JAVA_COMMAND); } // If no java command was specified, use default executable if (command == null) { File exe = null; if (fVMInstance instanceof StandardVM) { exe = ((StandardVM)fVMInstance).getJavaExecutable(); } else { exe = StandardVMType.findJavaExecutable(fVMInstance.getInstallLocation()); } if (exe == null) { abort(NLS.bind(LaunchingMessages.StandardVMRunner_Unable_to_locate_executable_for__0__1, new String[]{fVMInstance.getName()}), null, IJavaLaunchConfigurationConstants.ERR_INTERNAL_ERROR); } else { return exe.getAbsolutePath(); } } // Build the path to the java executable. First try 'bin', and if that // doesn't exist, try 'jre/bin' String installLocation = fVMInstance.getInstallLocation().getAbsolutePath() + File.separatorChar; File exe = new File(installLocation + "bin" + File.separatorChar + command); //$NON-NLS-1$ if (fileExists(exe)){ return exe.getAbsolutePath(); } exe = new File(exe.getAbsolutePath() + ".exe"); //$NON-NLS-1$ if (fileExists(exe)){ return exe.getAbsolutePath(); } exe = new File(installLocation + "jre" + File.separatorChar + "bin" + File.separatorChar + command); //$NON-NLS-1$ //$NON-NLS-2$ if (fileExists(exe)) { return exe.getAbsolutePath(); } exe = new File(exe.getAbsolutePath() + ".exe"); //$NON-NLS-1$ if (fileExists(exe)) { return exe.getAbsolutePath(); } // not found abort(NLS.bind(LaunchingMessages.StandardVMRunner_Specified_executable__0__does_not_exist_for__1__4, new String[]{command, fVMInstance.getName()}), null, IJavaLaunchConfigurationConstants.ERR_INTERNAL_ERROR); // NOTE: an exception will be thrown - null cannot be returned return null; }
Convenience method to determine if the specified file exists or not
Params:
  • file – the file to check
Returns:true if the file indeed exists, false otherwise
/** * Convenience method to determine if the specified file exists or not * @param file the file to check * @return true if the file indeed exists, false otherwise */
protected boolean fileExists(File file) { return file.exists() && file.isFile(); } protected String convertClassPath(String[] cp) { int pathCount= 0; StringBuilder buf= new StringBuilder(); if (cp.length == 0) { return ""; //$NON-NLS-1$ } for (int i= 0; i < cp.length; i++) { if (pathCount > 0) { buf.append(File.pathSeparator); } buf.append(cp[i]); pathCount++; } return buf.toString(); }
This method is used to ensure that the JVM file encoding matches that of the console preference for file encoding. If the user explicitly declares a file encoding in the launch configuration, then that file encoding is used.
Params:
  • launch – the Launch
  • vmargs – the original listing of JVM arguments
Returns:the listing of JVM arguments including file encoding if one was not specified
Since:3.4
/** * This method is used to ensure that the JVM file encoding matches that of the console preference for file encoding. * If the user explicitly declares a file encoding in the launch configuration, then that file encoding is used. * * @param launch the {@link Launch} * @param vmargs the original listing of JVM arguments * @return the listing of JVM arguments including file encoding if one was not specified * * @since 3.4 */
protected String[] ensureEncoding(ILaunch launch, String[] vmargs) { boolean foundencoding = false; for(int i = 0; i < vmargs.length; i++) { if(vmargs[i].startsWith("-Dfile.encoding=")) { //$NON-NLS-1$ foundencoding = true; } } if(!foundencoding) { String encoding = launch.getAttribute(DebugPlugin.ATTR_CONSOLE_ENCODING); if(encoding == null) { return vmargs; } String[] newargs = new String[vmargs.length+1]; System.arraycopy(vmargs, 0, newargs, 0, vmargs.length); newargs[newargs.length-1] = "-Dfile.encoding="+encoding; //$NON-NLS-1$ return newargs; } return vmargs; } protected class CommandDetails { private String[] commandLine; private String[] envp; private File workingDir; private ClasspathShortener classpathShortener; private int port; public String[] getEnvp() { return envp; } public void setEnvp(String[] envp) { this.envp = envp; } public String[] getCommandLine() { return commandLine; } public void setCommandLine(String[] commandLine) { this.commandLine = commandLine; } public File getWorkingDir() { return workingDir; } public void setWorkingDir(File workingDir) { this.workingDir = workingDir; } public ClasspathShortener getClasspathShortener() { return classpathShortener; } public void setClasspathShortener(ClasspathShortener classpathShortener) { this.classpathShortener = classpathShortener; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } } @Override public String showCommandLine(VMRunnerConfiguration configuration, ILaunch launch, IProgressMonitor monitor) throws CoreException { IProgressMonitor subMonitor = SubMonitor.convert(monitor, 1); CommandDetails cmd = getCommandLine(configuration, launch, subMonitor); if (subMonitor.isCanceled() || cmd == null) { return ""; //$NON-NLS-1$ } String[] cmdLine = cmd.getCommandLine(); cmdLine = quoteWindowsArgs(cmdLine); return getCmdLineAsString(cmdLine); } private CommandDetails getCommandLine(VMRunnerConfiguration config, ILaunch launch, IProgressMonitor monitor) throws CoreException { IProgressMonitor subMonitor = SubMonitor.convert(monitor, 1); subMonitor.subTask(LaunchingMessages.StandardVMRunner_Constructing_command_line____2); String program = constructProgramString(config); List<String> arguments= new ArrayList<>(); arguments.add(program); // VM args are the first thing after the java program so that users can specify // options like '-client' & '-server' which are required to be the first option String[] allVMArgs = combineVmArgs(config, fVMInstance); addArguments(ensureEncoding(launch, allVMArgs), arguments); addBootClassPathArguments(arguments, config); String[] mp = config.getModulepath(); if (mp != null && mp.length > 0) { // There can be scenarios like junit where launched class is in classpath // with modular path entries arguments.add("-p"); //$NON-NLS-1$ arguments.add(convertClassPath(mp)); } String[] cp = config.getClassPath(); if (cp.length > 0) { arguments.add("-classpath"); //$NON-NLS-1$ arguments.add(convertClassPath(cp)); } // https://openjdk.java.net/jeps/12 if (config.isPreviewEnabled()) { arguments.add("--enable-preview"); //$NON-NLS-1$ } String dependencies = config.getOverrideDependencies(); if (dependencies != null && dependencies.length() > 0) { String[] parseArguments = DebugPlugin.parseArguments(dependencies); for (String string : parseArguments) { arguments.add(string); } } if (isModular(config, fVMInstance)) { arguments.add("-m"); //$NON-NLS-1$ arguments.add(config.getModuleDescription() + "/" + config.getClassToLaunch()); //$NON-NLS-1$ } else { arguments.add(config.getClassToLaunch()); } int lastVMArgumentIndex = arguments.size() - 1; String[] programArgs= config.getProgramArguments(); addArguments(programArgs, arguments); String[] envp = prependJREPath(config.getEnvironment()); String[] cmdLine= new String[arguments.size()]; arguments.toArray(cmdLine); File workingDir = getWorkingDir(config); ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp); if (classpathShortener.shortenCommandLineIfNecessary()) { cmdLine = classpathShortener.getCmdLine(); envp = classpathShortener.getEnvp(); } String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine); if (newCmdLine != null) { cmdLine = newCmdLine; } CommandDetails cmd = new CommandDetails(); cmd.setCommandLine(cmdLine); cmd.setEnvp(envp); cmd.setWorkingDir(workingDir); cmd.setClasspathShortener(classpathShortener); subMonitor.worked(1); return cmd; } @Override public void run(VMRunnerConfiguration config, ILaunch launch, IProgressMonitor monitor) throws CoreException { IProgressMonitor subMonitor = SubMonitor.convert(monitor, 1); CommandDetails cmdDetails = getCommandLine(config, launch, subMonitor); // check for cancellation if (subMonitor.isCanceled() || cmdDetails == null) { return; } String[] cmdLine = cmdDetails.getCommandLine(); subMonitor.beginTask(LaunchingMessages.StandardVMRunner_Launching_VM____1, 2); subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3); Process p = null; p = exec(cmdLine, cmdDetails.getWorkingDir(), cmdDetails.getEnvp(), config.isMergeOutput()); if (p == null) { return; } // check for cancellation if (subMonitor.isCanceled()) { p.destroy(); return; } String timestamp = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date(System.currentTimeMillis())); IProcess process= newProcess(launch, p, renderProcessLabel(cmdLine, timestamp), getDefaultProcessMap()); process.setAttribute(DebugPlugin.ATTR_PATH, cmdLine[0]); process.setAttribute(IProcess.ATTR_CMDLINE, renderCommandLine(cmdLine)); String ltime = launch.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP); process.setAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP, ltime != null ? ltime : timestamp); if (cmdDetails.getWorkingDir() != null) { process.setAttribute(DebugPlugin.ATTR_WORKING_DIRECTORY, cmdDetails.getWorkingDir().getAbsolutePath()); } if (cmdDetails.getEnvp() != null) { String[] envp = cmdDetails.getEnvp(); Arrays.sort(envp); StringBuilder buff = new StringBuilder(); for (int i = 0; i < envp.length; i++) { buff.append(envp[i]); if(i < envp.length-1) { buff.append('\n'); } } process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString()); } if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()) { String tempFiles = cmdDetails.getClasspathShortener().getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator)); process.setAttribute(LaunchingPlugin.ATTR_LAUNCH_TEMP_FILES, tempFiles); } subMonitor.worked(1); subMonitor.done(); }
Returns the index in the given array for the CLASSPATH variable
Params:
  • env – the environment array or null
Returns:-1 or the index of the CLASSPATH variable
Since:3.6.200
/** * Returns the index in the given array for the CLASSPATH variable * @param env the environment array or <code>null</code> * @return -1 or the index of the CLASSPATH variable * @since 3.6.200 */
int getCPIndex(String[] env) { if(env != null) { for (int i = 0; i < env.length; i++) { if(env[i].regionMatches(true, 0, "CLASSPATH=", 0, 10)) { //$NON-NLS-1$ return i; } } } return -1; }
Prepends the correct java version variable state to the environment path for Mac VMs
Params:
  • env – the current array of environment variables to run with
Returns:the new path segments
Since:3.3
/** * Prepends the correct java version variable state to the environment path for Mac VMs * * @param env the current array of environment variables to run with * @return the new path segments * @since 3.3 */
protected String[] prependJREPath(String[] env) { if (Platform.OS_MACOSX.equals(Platform.getOS())) { if (fVMInstance instanceof IVMInstall2) { IVMInstall2 vm = (IVMInstall2) fVMInstance; String javaVersion = vm.getJavaVersion(); if (javaVersion != null) { if (env == null) { Map<String, String> map = DebugPlugin.getDefault().getLaunchManager().getNativeEnvironmentCasePreserved(); if (map.containsKey(StandardVMDebugger.JAVA_JVM_VERSION)) { String[] env2 = new String[map.size()]; Iterator<Entry<String, String>> iterator = map.entrySet().iterator(); int i = 0; while (iterator.hasNext()) { Entry<String, String> entry = iterator.next(); String key = entry.getKey(); if (StandardVMDebugger.JAVA_JVM_VERSION.equals(key)) { env2[i] = key + "=" + javaVersion; //$NON-NLS-1$ } else { env2[i] = key + "=" + entry.getValue(); //$NON-NLS-1$ } i++; } env = env2; } } else { for (int i = 0; i < env.length; i++) { String string = env[i]; if (string.startsWith(StandardVMDebugger.JAVA_JVM_VERSION)) { env[i]=StandardVMDebugger.JAVA_JVM_VERSION+"="+javaVersion; //$NON-NLS-1$ break; } } } } } } return env; }
Adds arguments to the bootpath
Params:
  • arguments – the arguments
  • config – the VM config
/** * Adds arguments to the bootpath * @param arguments the arguments * @param config the VM config */
protected void addBootClassPathArguments(List<String> arguments, VMRunnerConfiguration config) { String[] prependBootCP= null; String[] bootCP= null; String[] appendBootCP= null; Map<String, Object> map = config.getVMSpecificAttributesMap(); if (map != null) { prependBootCP = (String[]) map.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_PREPEND); bootCP = (String[]) map.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH); if (JavaRuntime.isModularJava(fVMInstance)) { if (prependBootCP != null && prependBootCP.length > 0) { prependBootCP = null; LaunchingPlugin.log(LaunchingMessages.RunnerBootpathPError); } if (bootCP != null && bootCP.length > 0) { bootCP = null; LaunchingPlugin.log(LaunchingMessages.RunnerBootpathError); } } appendBootCP= (String[]) map.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_APPEND); } if (!JavaRuntime.isModularJava(fVMInstance)) { if (prependBootCP == null && bootCP == null && appendBootCP == null) { // use old single attribute instead of new attributes if not specified bootCP = config.getBootClassPath(); } } if (prependBootCP != null) { arguments.add("-Xbootclasspath/p:" + convertClassPath(prependBootCP)); //$NON-NLS-1$ } if (bootCP != null) { if (bootCP.length > 0) { arguments.add("-Xbootclasspath:" + convertClassPath(bootCP)); //$NON-NLS-1$ } } if (appendBootCP != null) { arguments.add("-Xbootclasspath/a:" + convertClassPath(appendBootCP)); //$NON-NLS-1$ } } }