blob: 0b8d17368c45b8d68c96aaa954ce7baa810d2689 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.launching;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
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.VMRunnerConfiguration;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.MessageFormat;
/**
* A launcher for running Java main classes.
*/
public class StandardVMRunner extends AbstractVMRunner {
/**
* The VM install instance
*/
protected IVMInstall fVMInstance;
/**
* Constructor
* @param vmInstance
*/
public StandardVMRunner(IVMInstall vmInstance) {
fVMInstance= vmInstance;
}
/**
* Returns the 'rendered' name for the current target
* @param classToRun
* @param host
* @return the name for the current target
*/
protected String renderDebugTarget(String classToRun, int host) {
String format= LaunchingMessages.StandardVMRunner__0__at_localhost__1__1;
return MessageFormat.format(format, new String[] { classToRun, String.valueOf(host) });
}
/**
* Returns the 'rendered' name for the specified command line
* @param commandLine
* @return the name for the process
*/
public static String renderProcessLabel(String[] commandLine) {
String format= LaunchingMessages.StandardVMRunner__0____1___2;
String timestamp= DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date(System.currentTimeMillis()));
return MessageFormat.format(format, new String[] { commandLine[0], timestamp });
}
/**
* Prepares the command line from the specified array of strings
* @param commandLine
* @return
*/
protected String renderCommandLine(String[] commandLine) {
if (commandLine.length < 1)
return ""; //$NON-NLS-1$
StringBuffer buf= new StringBuffer();
for (int i= 0; i < commandLine.length; i++) {
buf.append(' ');
char[] characters= commandLine[i].toCharArray();
StringBuffer command= new StringBuffer();
boolean containsSpace= false;
for (int j = 0; j < characters.length; j++) {
char character= characters[j];
if (character == '\"') {
command.append('\\');
} else if (character == ' ') {
containsSpace = true;
}
command.append(character);
}
if (containsSpace) {
buf.append('\"');
buf.append(command.toString());
buf.append('\"');
} else {
buf.append(command.toString());
}
}
return buf.toString();
}
/**
* Adds the values of args to the given list v
* @param args
* @param v
*/
protected void addArguments(String[] args, List v) {
if (args == null) {
return;
}
for (int i= 0; i < args.length; i++) {
v.add(args[i]);
}
}
/**
* 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.
*
* @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(MessageFormat.format(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;
}
/**
* @see VMRunner#getPluginIdentifier()
*/
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.
*
* @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 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(MessageFormat.format(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(MessageFormat.format(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
* @param file
* @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;
StringBuffer buf= new StringBuffer();
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.
* @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;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IVMRunner#run(org.eclipse.jdt.launching.VMRunnerConfiguration, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
*/
public void run(VMRunnerConfiguration config, ILaunch launch, IProgressMonitor monitor) throws CoreException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1);
subMonitor.beginTask(LaunchingMessages.StandardVMRunner_Launching_VM____1, 2);
subMonitor.subTask(LaunchingMessages.StandardVMRunner_Constructing_command_line____2);
String program= constructProgramString(config);
List 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[] cp= config.getClassPath();
int cpidx = -1;
if (cp.length > 0) {
cpidx = arguments.size();
arguments.add("-classpath"); //$NON-NLS-1$
arguments.add(convertClassPath(cp));
}
arguments.add(config.getClassToLaunch());
String[] programArgs= config.getProgramArguments();
addArguments(programArgs, arguments);
String[] envp = prependJREPath(config.getEnvironment());
String[] newenvp = checkClasspath(arguments, cp, envp);
if(newenvp != null) {
envp = newenvp;
arguments.remove(cpidx);
arguments.remove(cpidx);
}
String[] cmdLine= new String[arguments.size()];
arguments.toArray(cmdLine);
subMonitor.worked(1);
// check for cancellation
if (monitor.isCanceled()) {
return;
}
subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3);
Process p= null;
File workingDir = getWorkingDir(config);
p= exec(cmdLine, workingDir, envp);
if (p == null) {
return;
}
// check for cancellation
if (monitor.isCanceled()) {
p.destroy();
return;
}
IProcess process= newProcess(launch, p, renderProcessLabel(cmdLine), getDefaultProcessMap());
process.setAttribute(IProcess.ATTR_CMDLINE, renderCommandLine(cmdLine));
subMonitor.worked(1);
subMonitor.done();
}
/**
* 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;
}
/**
* Checks to see if the command / classpath needs to be shortened for Windows. Returns the modified
* environment or <code>null</code> if no changes are needed.
*
* @param args the raw arguments from the runner
* @param cp the raw classpath from the runner configuration
* @param env the current environment
* @return the modified environment or <code>null</code> if no changes were made
* @sine 3.6.200
*/
String[] checkClasspath(List<String> args, String[] cp, String[] env) {
if(Platform.getOS().equals(Platform.OS_WIN32)) {
//count the complete command length
int size = 0;
for (String arg : args) {
if(arg != null) {
size += arg.length();
}
}
//greater than 32767 is a no-go
//see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
if(size > 32767) {
StringBuffer newcp = new StringBuffer("CLASSPATH="); //$NON-NLS-1$
for (int i = 0; i < cp.length; i++) {
newcp.append(cp[i]);
newcp.append(File.pathSeparatorChar);
}
String[] newenvp = null;
int index = -1;
if(env == null) {
Map<String, String> nenv = DebugPlugin.getDefault().getLaunchManager().getNativeEnvironment();
Entry<String, String> entry = null;
newenvp = new String[nenv.size()];
int idx = 0;
for (Iterator<Entry<String, String>> i = nenv.entrySet().iterator(); i.hasNext();) {
entry = i.next();
String value = entry.getValue();
if(value == null) {
value = ""; //$NON-NLS-1$
}
String key = entry.getKey();
if(key.equalsIgnoreCase("CLASSPATH")) { //$NON-NLS-1$
index = idx;
}
newenvp[idx] = key+'='+value;
idx++;
}
}
else {
newenvp = env;
index = getCPIndex(newenvp);
}
if(index < 0) {
String[] newenv = new String[newenvp.length+1];
System.arraycopy(newenvp, 0, newenv, 0, newenvp.length);
newenv[newenvp.length] = newcp.toString();
return newenv;
}
newenvp[index] = newcp.toString();
return newenvp;
}
}
return null;
}
/**
* 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
* @param jdkpath the path of the current jdk
* @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 map = DebugPlugin.getDefault().getLaunchManager().getNativeEnvironmentCasePreserved();
if (map.containsKey(StandardVMDebugger.JAVA_JVM_VERSION)) {
String[] env2 = new String[map.size()];
Iterator iterator = map.entrySet().iterator();
int i = 0;
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
String key = (String) entry.getKey();
if (StandardVMDebugger.JAVA_JVM_VERSION.equals(key)) {
env2[i] = key + "=" + javaVersion; //$NON-NLS-1$
} else {
env2[i] = key + "=" + (String)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
* @param arguments
* @param config
*/
protected void addBootClassPathArguments(List arguments, VMRunnerConfiguration config) {
String[] prependBootCP= null;
String[] bootCP= null;
String[] appendBootCP= null;
Map map = config.getVMSpecificAttributesMap();
if (map != null) {
prependBootCP= (String[]) map.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_PREPEND);
bootCP= (String[]) map.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH);
appendBootCP= (String[]) map.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_APPEND);
}
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$
}
}
}