Bug 312397 - Run Configuration Command Line to Clipboard

Change-Id: Ia47080c8e9838bdde0ecbc59cd0822bdab3438ab
diff --git a/org.eclipse.jdt.launching/.settings/.api_filters b/org.eclipse.jdt.launching/.settings/.api_filters
index e437596..984d02b 100644
--- a/org.eclipse.jdt.launching/.settings/.api_filters
+++ b/org.eclipse.jdt.launching/.settings/.api_filters
@@ -8,6 +8,14 @@
             </message_arguments>
         </filter>
     </resource>
+    <resource path="launching/org/eclipse/jdt/launching/IVMRunner.java" type="org.eclipse.jdt.launching.IVMRunner">
+        <filter comment="added default method" id="404000815">
+            <message_arguments>
+                <message_argument value="org.eclipse.jdt.launching.IVMRunner"/>
+                <message_argument value="showCommandLine(VMRunnerConfiguration, ILaunch, IProgressMonitor)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="launching/org/eclipse/jdt/launching/sourcelookup/LocalFileStorage.java" type="org.eclipse.jdt.launching.sourcelookup.LocalFileStorage">
         <filter comment="Known illegal extension" id="571473929">
             <message_arguments>
diff --git a/org.eclipse.jdt.launching/META-INF/MANIFEST.MF b/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
index 80d67b5..65dda94 100644
--- a/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
@@ -16,7 +16,7 @@
  org.eclipse.jdt.launching.sourcelookup.containers
 Require-Bundle: org.eclipse.core.resources;bundle-version="[3.5.0,4.0.0)",
  org.eclipse.jdt.core;bundle-version="[3.8.0,4.0.0)",
- org.eclipse.debug.core;bundle-version="[3.12.0,4.0.0)",
+ org.eclipse.debug.core;bundle-version="[3.13.0,4.0.0)",
  org.eclipse.jdt.debug;bundle-version="[3.11.0,4.0.0)",
  org.eclipse.core.variables;bundle-version="[3.2.0,4.0.0)",
  org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
index 47060d2..e81a77b 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- *  Copyright (c) 2000, 2017 IBM Corporation and others.
+ *  Copyright (c) 2000, 2018 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
@@ -348,21 +348,65 @@
 		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)
-	 */
-	@Override
-	public void run(VMRunnerConfiguration config, ILaunch launch, IProgressMonitor monitor) throws CoreException {
+	private class CommandDetails {
+		private String[] commandLine;
+		private String[] envp;
+		private File workingDir;
+		private ClasspathShortener classpathShortener;
 
+		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;
+		}
+
+	}
+
+	@Override
+	public String showCommandLine(VMRunnerConfiguration configuration, ILaunch launch, IProgressMonitor monitor) throws CoreException {
+		CommandDetails cmd = getCommandLine(configuration, launch, monitor);
+		if (monitor.isCanceled()) {
+			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 {
 		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);
+		String program = constructProgramString(config);
 
 		List<String> arguments= new ArrayList<>();
 		arguments.add(program);
@@ -408,27 +452,49 @@
 
 		String[] cmdLine= new String[arguments.size()];
 		arguments.toArray(cmdLine);
-
-		subMonitor.worked(1);
-
-		// check for cancellation
-		if (monitor.isCanceled()) {
-			return;
-		}
 		File workingDir = getWorkingDir(config);
 		ClasspathShortener classpathShortener = new ClasspathShortener(fVMInstance, launch, cmdLine, lastVMArgumentIndex, workingDir, envp);
 		if (classpathShortener.shortenCommandLineIfNecessary()) {
 			cmdLine = classpathShortener.getCmdLine();
 			envp = classpathShortener.getEnvp();
 		}
+		CommandDetails cmd = new CommandDetails();
+		cmd.setCommandLine(cmdLine);
+		cmd.setEnvp(envp);
+		cmd.setWorkingDir(workingDir);
+		cmd.setClasspathShortener(classpathShortener);
+		subMonitor.worked(1);
+		return cmd;
+	}
 
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.eclipse.jdt.launching.IVMRunner#run(org.eclipse.jdt.launching.VMRunnerConfiguration, org.eclipse.debug.core.ILaunch,
+	 * org.eclipse.core.runtime.IProgressMonitor)
+	 */
+	@Override
+	public void run(VMRunnerConfiguration config, ILaunch launch, IProgressMonitor monitor) throws CoreException {
+
+
+		CommandDetails cmdDetails = getCommandLine(config, launch, monitor);
+		String[] cmdLine = cmdDetails.getCommandLine();
+
+
+		// check for cancellation
+		if (monitor.isCanceled()) {
+			return;
+		}
+
+		IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1);
+		subMonitor.beginTask(LaunchingMessages.StandardVMRunner_Launching_VM____1, 2);
 		subMonitor.subTask(LaunchingMessages.StandardVMRunner_Starting_virtual_machine____3);
 		Process p= null;
 		String[] newCmdLine = validateCommandLine(launch.getLaunchConfiguration(), cmdLine);
 		if(newCmdLine != null) {
 			cmdLine = newCmdLine;
 		}
-		p= exec(cmdLine, workingDir, envp);
+		p = exec(cmdLine, cmdDetails.getWorkingDir(), cmdDetails.getEnvp());
 		if (p == null) {
 			return;
 		}
@@ -444,10 +510,11 @@
 		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(workingDir != null) {
-			process.setAttribute(DebugPlugin.ATTR_WORKING_DIRECTORY, workingDir.getAbsolutePath());
+		if (cmdDetails.getWorkingDir() != null) {
+			process.setAttribute(DebugPlugin.ATTR_WORKING_DIRECTORY, cmdDetails.getWorkingDir().getAbsolutePath());
 		}
-		if(envp != null) {
+		if (cmdDetails.getEnvp() != null) {
+			String[] envp = cmdDetails.getEnvp();
 			Arrays.sort(envp);
 			StringBuilder buff = new StringBuilder();
 			for (int i = 0; i < envp.length; i++) {
@@ -458,8 +525,8 @@
 			}
 			process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
 		}
-		if (!classpathShortener.getProcessTempFiles().isEmpty()) {
-			String tempFiles = classpathShortener.getProcessTempFiles().stream().map(file -> file.getAbsolutePath()).collect(Collectors.joining(File.pathSeparator));
+		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);
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java
index 0adccbc..6f02618 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractVMRunner.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 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
@@ -92,7 +92,10 @@
 		return DebugPlugin.exec(cmdLine, workingDirectory, envp);
 	}
 
-	private static String[] quoteWindowsArgs(String[] cmdLine) {
+	/**
+	 * @since 3.11
+	 */
+	protected static String[] quoteWindowsArgs(String[] cmdLine) {
 		// see https://bugs.eclipse.org/387504 , workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6511002
 		if (Platform.getOS().equals(Constants.OS_WIN32)) {
 			String[] winCmdLine = new String[cmdLine.length];
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IVMRunner.java
index 0bb24ee..9a7ff6f 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IVMRunner.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IVMRunner.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * Copyright (c) 2000, 20018 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
@@ -41,4 +41,27 @@
 	 */
 	public void run(VMRunnerConfiguration configuration, ILaunch launch, IProgressMonitor monitor) throws CoreException;
 
+	/**
+	 * Gets the command line required to launch a Java VM as specified in the given configuration, contributing results (debug targets and processes),
+	 * to the given launch.
+	 *
+	 * @param configuration
+	 *            the configuration settings for this run
+	 * @param launch
+	 *            the launch to contribute to
+	 * @param monitor
+	 *            progress monitor or <code>null</code> A cancelable progress monitor is provided by the Job framework. It should be noted that the
+	 *            setCanceled(boolean) method should never be called on the provided monitor or the monitor passed to any delegates from this method;
+	 *            due to a limitation in the progress monitor framework using the setCanceled method can cause entire workspace batch jobs to be
+	 *            canceled, as the canceled flag is propagated up the top-level parent monitor. The provided monitor is not guaranteed to have been
+	 *            started.
+	 * @return the command line string
+	 * @exception CoreException
+	 *                if an exception occurs while getting the command line
+	 * @since 3.11
+	 */
+	public default String showCommandLine(VMRunnerConfiguration configuration, ILaunch launch, IProgressMonitor monitor) throws CoreException {
+		return ""; //$NON-NLS-1$
+	}
+
 }
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaLaunchDelegate.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaLaunchDelegate.java
index 8719c84..cf518cf 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaLaunchDelegate.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaLaunchDelegate.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2017 IBM Corporation and others.
+ * Copyright (c) 2000, 2018 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
@@ -35,86 +35,113 @@
  */
 public class JavaLaunchDelegate extends AbstractJavaLaunchConfigurationDelegate {
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.debug.core.model.ILaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
-	 */
 	@Override
-	public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
-
+	public String showCommandLine(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
 		if (monitor == null) {
 			monitor = new NullProgressMonitor();
 		}
+		try {
+			VMRunnerConfiguration runConfig = getVMRunnerConfiguration(configuration, mode, launch, monitor);
+			IVMRunner runner = getVMRunner(configuration, mode);
+			String cmdLine = runner.showCommandLine(runConfig, launch, monitor);
+
+			// check for cancellation
+			if (monitor.isCanceled()) {
+				return ""; //$NON-NLS-1$
+			}
+			return cmdLine;
+		} finally {
+			monitor.done();
+		}
+	}
+
+	private VMRunnerConfiguration getVMRunnerConfiguration(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
+
 
 		monitor.beginTask(NLS.bind("{0}...", new String[]{configuration.getName()}), 3); //$NON-NLS-1$
 		// check for cancellation
 		if (monitor.isCanceled()) {
-			return;
+			return null;
+		}
+		monitor.subTask(LaunchingMessages.JavaLocalApplicationLaunchConfigurationDelegate_Verifying_launch_attributes____1);
+
+		String mainTypeName = verifyMainTypeName(configuration);
+
+		File workingDir = verifyWorkingDirectory(configuration);
+		String workingDirName = null;
+		if (workingDir != null) {
+			workingDirName = workingDir.getAbsolutePath();
+		}
+
+		// Environment variables
+		String[] envp = getEnvironment(configuration);
+
+		// Program & VM arguments
+		String pgmArgs = getProgramArguments(configuration);
+		String vmArgs = concat(getVMArguments(configuration), getVMArguments(configuration, mode));
+		ExecutionArguments execArgs = new ExecutionArguments(vmArgs, pgmArgs);
+
+		// VM-specific attributes
+		Map<String, Object> vmAttributesMap = getVMSpecificAttributesMap(configuration);
+
+		// Bug 522333 :to be used for modulepath only for 4.7.*
+		String[][] paths = getClasspathAndModulepath(configuration);
+		// Create VM config
+		VMRunnerConfiguration runConfig = new VMRunnerConfiguration(mainTypeName, getClasspath(configuration));
+		runConfig.setProgramArguments(execArgs.getProgramArgumentsArray());
+		runConfig.setEnvironment(envp);
+		runConfig.setVMArguments(execArgs.getVMArgumentsArray());
+		runConfig.setWorkingDirectory(workingDirName);
+		runConfig.setVMSpecificAttributesMap(vmAttributesMap);
+		// current module name, if so
+		try {
+			IJavaProject proj = JavaRuntime.getJavaProject(configuration);
+			if (proj != null) {
+				IModuleDescription module = proj == null ? null : proj.getModuleDescription();
+				String modName = module == null ? null : module.getElementName();
+				if (modName != null) {
+					runConfig.setModuleDescription(modName);
+				}
+			}
+		} catch (CoreException e) {
+			// Not a java Project so no need to set module description
+		}
+
+		if (!JavaRuntime.isModularConfiguration(configuration)) {
+			// Bootpath
+			runConfig.setBootClassPath(getBootpath(configuration));
+		} else {
+			// module path
+			runConfig.setModulepath(paths[1]);
+			if (!configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_MODULE_CLI_OPTIONS, true)) {
+				runConfig.setOverrideDependencies(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MODULE_CLI_OPTIONS, "")); //$NON-NLS-1$
+			} else {
+				runConfig.setOverrideDependencies(getModuleCLIOptions(configuration));
+			}
+		}
+		// check for cancellation
+		if (monitor.isCanceled()) {
+			return null;
+		}
+		monitor.worked(1);
+
+		return runConfig;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 *
+	 * @see org.eclipse.debug.core.model.ILaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String,
+	 * org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
+	 */
+	@Override
+	public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException {
+		if (monitor == null) {
+			monitor = new NullProgressMonitor();
 		}
 		try {
-			monitor.subTask(LaunchingMessages.JavaLocalApplicationLaunchConfigurationDelegate_Verifying_launch_attributes____1);
 
-			String mainTypeName = verifyMainTypeName(configuration);
-			IVMRunner runner = getVMRunner(configuration, mode);
-
-			File workingDir = verifyWorkingDirectory(configuration);
-			String workingDirName = null;
-			if (workingDir != null) {
-				workingDirName = workingDir.getAbsolutePath();
-			}
-
-			// Environment variables
-			String[] envp= getEnvironment(configuration);
-
-			// Program & VM arguments
-			String pgmArgs = getProgramArguments(configuration);
-			String vmArgs = concat(getVMArguments(configuration), getVMArguments(configuration, mode));
-			ExecutionArguments execArgs = new ExecutionArguments(vmArgs, pgmArgs);
-
-			// VM-specific attributes
-			Map<String, Object> vmAttributesMap = getVMSpecificAttributesMap(configuration);
-
-			// Bug 522333 :to be used for modulepath only for 4.7.*
-			String[][] paths = getClasspathAndModulepath(configuration);
-			// Create VM config
-			VMRunnerConfiguration runConfig = new VMRunnerConfiguration(mainTypeName, getClasspath(configuration));
-			runConfig.setProgramArguments(execArgs.getProgramArgumentsArray());
-			runConfig.setEnvironment(envp);
-			runConfig.setVMArguments(execArgs.getVMArgumentsArray());
-			runConfig.setWorkingDirectory(workingDirName);
-			runConfig.setVMSpecificAttributesMap(vmAttributesMap);
-			// current module name, if so
-			try {
-				IJavaProject proj = JavaRuntime.getJavaProject(configuration);
-				if (proj != null) {
-					IModuleDescription module = proj == null ? null : proj.getModuleDescription();
-					String modName = module == null ? null : module.getElementName();
-					if (modName != null) {
-						runConfig.setModuleDescription(modName);
-					}
-				}
-			}
-			catch (CoreException e) {
-				// Not a java Project so no need to set module description
-			}
-
-			if (!JavaRuntime.isModularConfiguration(configuration)) {
-				// Bootpath
-				runConfig.setBootClassPath(getBootpath(configuration));
-			} else {
-				// module path
-				runConfig.setModulepath(paths[1]);
-				if (!configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_MODULE_CLI_OPTIONS, true)) {
-					runConfig.setOverrideDependencies(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MODULE_CLI_OPTIONS, "")); //$NON-NLS-1$
-				} else {
-					runConfig.setOverrideDependencies(getModuleCLIOptions(configuration));
-				}
-			}
-
-			// check for cancellation
-			if (monitor.isCanceled()) {
-				return;
-			}
-
+			VMRunnerConfiguration runConfig = getVMRunnerConfiguration(configuration, mode, launch, monitor);
 			// stop in main
 			prepareStopInMain(configuration);
 
@@ -125,8 +152,8 @@
 			// set the default source locator if required
 			setDefaultSourceLocator(launch, configuration);
 			monitor.worked(1);
-
 			// Launch the configuration - 1 unit of work
+			IVMRunner runner = getVMRunner(configuration, mode);
 			runner.run(runConfig, launch, monitor);
 
 			// check for cancellation
diff --git a/org.eclipse.jdt.launching/plugin.xml b/org.eclipse.jdt.launching/plugin.xml
index d028512..1730aae 100644
--- a/org.eclipse.jdt.launching/plugin.xml
+++ b/org.eclipse.jdt.launching/plugin.xml
@@ -46,6 +46,7 @@
          point="org.eclipse.debug.core.launchConfigurationTypes">
       <launchConfigurationType
             allowPrototypes="true"
+            allowCommandLine="true"
             delegate="org.eclipse.jdt.launching.sourcelookup.advanced.AdvancedJavaLaunchDelegate"
             delegateDescription="%localJavaApplicationDelegate.description"
             delegateName="%eclipseJDTLauncher.name"