Bug 565902 - Support for argument files during launch

Adds a new setting to the arguments tab for writing arguments into a
file. This allows for endless long command lines when launching Java
applications.

Change-Id: I63702eaaee565cd87fa881d80d4719371920432e
Bug: 565902
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 28d8701..6e5a95b 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -55,6 +55,7 @@
 import org.eclipse.jdt.debug.tests.console.JavaStackTraceConsoleTest;
 import org.eclipse.jdt.debug.tests.core.AlternateStratumTests;
 import org.eclipse.jdt.debug.tests.core.ArgumentTests;
+import org.eclipse.jdt.debug.tests.core.ArgumentTestsWithArgfile;
 import org.eclipse.jdt.debug.tests.core.ArrayTests;
 import org.eclipse.jdt.debug.tests.core.BootpathTests;
 import org.eclipse.jdt.debug.tests.core.ClasspathContainerTests;
@@ -243,6 +244,7 @@
 		addTest(new TestSuite(EnvironmentTests.class));
 		addTest(new TestSuite(ExecutionEnvironmentTests.class));
 		addTest(new TestSuite(ArgumentTests.class));
+		addTest(new TestSuite(ArgumentTestsWithArgfile.class));
 
 	//Console tests
 		addTest(new TestSuite(ConsoleTests.class));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java
index 3c22791..50a9967 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTests.java
@@ -13,13 +13,20 @@
  *******************************************************************************/
 package org.eclipse.jdt.debug.tests.core;
 
+import static java.util.stream.Collectors.joining;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import org.eclipse.core.resources.IContainer;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.core.DebugPlugin;
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.debug.core.ILaunchConfigurationType;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
@@ -42,6 +49,7 @@
 public class ArgumentTests extends AbstractDebugTest {
 
     private Object fLock = new Object();
+	protected boolean fUseArgfile = false;
 
 	private class ConsoleArgumentOutputRetriever implements IConsoleLineTrackerExtension {
 
@@ -343,20 +351,23 @@
 	 */
 	private void testOutput(String mainTypeName, String vmArgs, String programArgs, String outputValue) throws CoreException {
 		ILaunchConfigurationWorkingCopy workingCopy = newConfiguration(null, "config1");
+
 		workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, get14Project().getProject().getName());
 		workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, mainTypeName);
 		workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_STOP_IN_MAIN, true);
 		workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, vmArgs);
 		workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS, programArgs);
+		workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, fUseArgfile);
 
-    Map<String, String> env = getLaunchManager().getNativeEnvironment().entrySet().stream()
-  		.filter(e -> !"JAVA_TOOL_OPTIONS".equals(e.getKey()))
-  		.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-    workingCopy.setAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, false);
+		Map<String, String> env = getLaunchManager().getNativeEnvironment().entrySet().stream().filter(e -> !"JAVA_TOOL_OPTIONS".equals(e.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+		workingCopy.setAttribute(ILaunchManager.ATTR_APPEND_ENVIRONMENT_VARIABLES, false);
 		workingCopy.setAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES, env);
 
 		IVMInstall vm = JavaRuntime.getVMInstall(get14Project());
 		assertNotNull("shold be able to get the default VM install from the 1.4 project", vm);
+		if (fUseArgfile) {
+			assertTrue("test requires a JVM >= 9", JavaRuntime.isModularJava(vm));
+		}
 		//workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, JavaRuntime.newJREContainerPath(vm).toPortableString());
 
 		// use 'java' instead of 'javaw' to launch tests (javaw is problematic on JDK1.4.2)
@@ -368,23 +379,37 @@
 		ConsoleLineTracker.setDelegate(retriever);
 		IProcess process = null;
 		ILaunch launch = null;
+		String commandLine = null;
 		try {
 			HashSet<String> set = new HashSet<>();
 			set.add(ILaunchManager.RUN_MODE);
 			ensurePreferredDelegate(workingCopy, set);
 			launch = workingCopy.launch(ILaunchManager.RUN_MODE, null);
 			process = launch.getProcesses()[0];
+			commandLine = process.getAttribute(IProcess.ATTR_CMDLINE);
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
+		assertNotNull(commandLine);
+		if (!fUseArgfile) {
+			assertTrue("command line must not contain an @argfile", commandLine.indexOf(" @") == -1);
+		} else {
+			assertTrue("command line must contain an @argfile", commandLine.indexOf(" @") > -1);
+		}
 		try {
 			String output = retriever.getOutput();
 			// output if in error
 			if (!outputValue.equals(output)) {
 			    System.out.println();
 			    System.out.println(getName());
-				System.out.println("\tExpected: " + outputValue);
-				System.out.println("\tActual:   " + output);
+				System.out.println("\tExpected:     " + outputValue);
+				System.out.println("\tActual:       " + output);
+				System.out.println("\tCommand Line: " + commandLine);
+				if (fUseArgfile) {
+					System.out.println("\tArgfile:      "
+							+ readArgfile(commandLine).stream().collect(joining("\n\t              ")));
+					System.out.println();
+				}
 			}
 			assertEquals(outputValue, output);
 		} finally {
@@ -398,6 +423,18 @@
 		}
 	}
 
+	private List<String> readArgfile(String commandLine) {
+		String[] arguments = DebugPlugin.parseArguments(commandLine);
+		assertEquals("command line too long, only command @argfile expected", 2, arguments.length);
+		String argfile = arguments[1];
+		assertTrue("wrong command line, expected @argfile at index 1: " + commandLine, argfile != null && argfile.startsWith("@"));
+		try {
+			return Files.readAllLines(Path.of(argfile.substring(1)));
+		} catch (IOException e) {
+			throw new IllegalStateException("Error reading @argfile: " + argfile, e);
+		}
+	}
+
 	/**
 	 * Tests the default VM args
 	 * @throws CoreException
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTestsWithArgfile.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTestsWithArgfile.java
new file mode 100644
index 0000000..ddd03ae
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/core/ArgumentTestsWithArgfile.java
@@ -0,0 +1,10 @@
+package org.eclipse.jdt.debug.tests.core;
+
+public class ArgumentTestsWithArgfile extends ArgumentTests {
+
+	public ArgumentTestsWithArgfile(String name) {
+		super(name);
+		fUseArgfile = true;
+	}
+
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java
index 093772a..e1c3d37 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/debug/ui/launchConfigurations/JavaArgumentsTab.java
@@ -320,6 +320,7 @@
 		getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, LauncherMessages.JavaArgumentsTab_AttributeLabel_VMArguments);
 		getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_USE_START_ON_FIRST_THREAD, LauncherMessages.JavaArgumentsTab_AttributeLabel_UseAtStart);
 		getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES, LauncherMessages.JavaArgumentsTab_AttributeLabel_ActivateHelpfulNullPointerExceptions);
+		getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, LauncherMessages.JavaArgumentsTab_AttributeLabel_UseArgfile);
 		getAttributesLabelsForPrototype().put(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, LauncherMessages.JavaArgumentsTab_AttributeLabel_WorkingDirectory);
 	}
 }
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
index eea647b..ef34e7d 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.java
@@ -31,6 +31,8 @@
 	public static String JavaArgumentsTab_AttributeLabel_UseAtStart;
 	public static String JavaArgumentsTab_AttributeLabel_ActivateHelpfulNullPointerExceptions;
 	public static String JavaArgumentsTab_AttributeTooltip_ActivateHelpfulNullPointerExceptions;
+	public static String JavaArgumentsTab_AttributeLabel_UseArgfile;
+	public static String JavaArgumentsTab_AttributeTooltip_UseArgfile;
 
 	public static String JavaArgumentsTab_AttributeLabel_WorkingDirectory;
 
@@ -55,6 +57,7 @@
 	public static String VMArgumentsBlock_0;
 	public static String VMArgumentsBlock_1;
 	public static String VMArgumentsBlock_2;
+	public static String VMArgumentsBlock_3;
 
 	public static String VMArgumentsBlock_VM_Arguments;
 	public static String VMArgumentsBlock_4;
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
index 02c59f3..fac6a6f 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/LauncherMessages.properties
@@ -24,7 +24,9 @@
 JavaArgumentsTab_AttributeLabel_VMArguments=VM arguments
 JavaArgumentsTab_AttributeLabel_UseAtStart=Use at start on 1st thread
 JavaArgumentsTab_AttributeLabel_WorkingDirectory=WorkingDirectory
+JavaArgumentsTab_AttributeLabel_UseArgfile=Use @&argfile
 JavaArgumentsTab_AttributeTooltip_ActivateHelpfulNullPointerExceptions=You need at least Java14 to make use of it!
+JavaArgumentsTab_AttributeTooltip_UseArgfile=Write all arguments into an @argfile to allow an unlimited number when launching with Java 9 or higher.
 
 RuntimeClasspathAdvancedDialog_6=Add &Variable String:
 RuntimeClasspathAdvancedDialog_7=Va&riables...
@@ -32,6 +34,7 @@
 VMArgumentsBlock_0=Use the -&XstartOnFirstThread argument when launching with SWT
 VMArgumentsBlock_1=Use temporary JAR to speci&fy classpath (to avoid classpath length limitations)
 VMArgumentsBlock_2=Use the -XX:+ShowCode&DetailsInExceptionMessages argument when launching
+VMArgumentsBlock_3=Use @&argfile when launching
 VMArgumentsBlock_VM_Arguments=VM Arguments
 
 JavaConnectTab__Allow_termination_of_remote_VM_6=&Allow termination of remote VM
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java
index b99745e..40f2dd1 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/launcher/VMArgumentsBlock.java
@@ -46,6 +46,7 @@
 	protected Text fVMArgumentsText;
 	private Button fUseStartOnFirstThread = null;
 	private Button fHelpfulExceptions = null;
+	private Button fUseArgfile = null;
 	private Button fPgrmArgVariableButton;
 
 	/**
@@ -122,6 +123,15 @@
 				scheduleUpdateJob();
 			}
 		});
+		fUseArgfile = SWTFactory.createCheckButton(group, LauncherMessages.VMArgumentsBlock_3, null, true, 1);
+		fUseArgfile.setEnabled(false);
+		fUseArgfile.setToolTipText(LauncherMessages.JavaArgumentsTab_AttributeTooltip_UseArgfile);
+		fUseArgfile.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				scheduleUpdateJob();
+			}
+		});
 	}
 
 	/**
@@ -132,6 +142,7 @@
 		configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_ARGUMENTS, (String)null);
 		configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_START_ON_FIRST_THREAD, true);
 		configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES, true);
+		configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, false);
 	}
 
 	/**
@@ -147,6 +158,9 @@
 			if (fHelpfulExceptions != null) {
 				fHelpfulExceptions.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES, true));
 			}
+			if (fUseArgfile != null) {
+				fUseArgfile.setSelection(configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, false));
+			}
 		} catch (CoreException e) {
 			setErrorMessage(LauncherMessages.JavaArgumentsTab_Exception_occurred_reading_configuration___15 + e.getStatus().getMessage());
 			JDIDebugUIPlugin.log(e);
@@ -168,6 +182,12 @@
 		} else {
 			fHelpfulExceptions.setEnabled(false);
 		}
+		if (isJavaNewerThan(configuration, JavaCore.VERSION_1_8)) {
+			configuration.setAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, fUseArgfile.getSelection());
+			fUseArgfile.setEnabled(true);
+		} else {
+			fUseArgfile.setEnabled(false);
+		}
 	}
 
 	/**
diff --git a/org.eclipse.jdt.launching/META-INF/MANIFEST.MF b/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
index 9af398f..1fee4af 100644
--- a/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.launching; singleton:=true
-Bundle-Version: 3.18.100.qualifier
+Bundle-Version: 3.19.0.qualifier
 Bundle-Activator: org.eclipse.jdt.internal.launching.LaunchingPlugin
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineQuoting.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineQuoting.java
new file mode 100644
index 0000000..9ca92a7
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineQuoting.java
@@ -0,0 +1,60 @@
+package org.eclipse.jdt.internal.launching;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.osgi.service.environment.Constants;
+
+/**
+ * Utility for quoting of command line Arguments
+ */
+public class CommandLineQuoting {
+
+	private CommandLineQuoting() {
+		// empty
+	}
+
+	public 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];
+			if (cmdLine.length > 0) {
+				winCmdLine[0] = cmdLine[0];
+			}
+			for (int i = 1; i < cmdLine.length; i++) {
+				winCmdLine[i] = winQuote(cmdLine[i]);
+			}
+			cmdLine = winCmdLine;
+		}
+		return cmdLine;
+	}
+
+	static boolean needsQuoting(String s) {
+		int len = s.length();
+		if (len == 0) {
+			return true;
+		}
+		if ("\"\"".equals(s)) //$NON-NLS-1$
+		{
+			return false; // empty quotes must not be quoted again
+		}
+		for (int i = 0; i < len; i++) {
+			switch (s.charAt(i)) {
+				case ' ':
+				case '\t':
+				case '\\':
+				case '"':
+					return true;
+			}
+		}
+		return false;
+	}
+
+	private static String winQuote(String s) {
+		if (!needsQuoting(s)) {
+			return s;
+		}
+		s = s.replaceAll("([\\\\]*)\"", "$1$1\\\\\""); //$NON-NLS-1$ //$NON-NLS-2$
+		s = s.replaceAll("([\\\\]*)\\z", "$1$1"); //$NON-NLS-1$ //$NON-NLS-2$
+		return "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+	}
+
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineShortener.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineShortener.java
new file mode 100644
index 0000000..22fb6f4
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/CommandLineShortener.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Gunnar Wagenknecht 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:
+ *     Gunnar Wagenknecht - copied from ClasspathShortener and simplified to shorten all arguments
+ *******************************************************************************/
+package org.eclipse.jdt.internal.launching;
+
+import static org.eclipse.jdt.internal.launching.LaunchingPlugin.LAUNCH_TEMP_FILE_PREFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.debug.core.DebugPlugin;
+import org.eclipse.debug.core.ILaunch;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
+import org.eclipse.jdt.launching.IVMInstall;
+import org.eclipse.jdt.launching.IVMInstall2;
+
+/**
+ * Shortens the command line by writing all commands into an arguments file.
+ */
+public class CommandLineShortener {
+	public static String getJavaVersion(IVMInstall vmInstall) {
+		if (vmInstall instanceof IVMInstall2) {
+			IVMInstall2 install = (IVMInstall2) vmInstall;
+			return install.getJavaVersion();
+		}
+		return null;
+	}
+
+	private final String javaVersion;
+	private final ILaunch launch;
+	private final String[] cmdLine;
+	private File processTempFilesDir;
+
+	private final List<File> processTempFiles = new ArrayList<>();
+
+	/**
+	 *
+	 * @param vmInstall
+	 *            the vm installation
+	 * @param launch
+	 *            the launch
+	 * @param cmdLine
+	 *            the command line (java executable + VM arguments + program arguments)
+	 * @param lastJavaArgumentIndex
+	 *            the index of the last java argument in cmdLine (next arguments if any are program arguments)
+	 * @param workingDir
+	 *            the working dir to use for the launched VM or null
+	 * @param envp
+	 *            array of strings, each element of which has environment variable settings in the format name=value, or null if the subprocess should
+	 *            inherit the environment of the current process.
+	 */
+	public CommandLineShortener(IVMInstall vmInstall, ILaunch launch, String[] cmdLine, File workingDir) {
+		this(getJavaVersion(vmInstall), launch, cmdLine, workingDir);
+	}
+
+	protected CommandLineShortener(String javaVersion, ILaunch launch, String[] cmdLine, File workingDir) {
+		Assert.isNotNull(javaVersion);
+		Assert.isNotNull(launch);
+		Assert.isNotNull(cmdLine);
+		this.javaVersion = javaVersion;
+		this.launch = launch;
+		this.cmdLine = cmdLine;
+		this.processTempFilesDir = workingDir != null ? workingDir : Paths.get(".").toAbsolutePath().normalize().toFile(); //$NON-NLS-1$
+	}
+
+	protected void addProcessTempFile(File file) {
+		processTempFiles.add(file);
+	}
+
+	protected File createArgumentFile(String[] cmdLine) throws CoreException {
+		try {
+			String timeStamp = getLaunchTimeStamp();
+			File argumentsFile = new File(processTempFilesDir, String.format(LAUNCH_TEMP_FILE_PREFIX
+					+ "%s-args-%s.txt", getLaunchConfigurationName(), timeStamp)); //$NON-NLS-1$
+
+			cmdLine = quoteForArgfile(cmdLine);
+
+			Files.write(argumentsFile.toPath(), Arrays.asList(cmdLine));
+			return argumentsFile;
+		} catch (IOException e) {
+			throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IStatus.ERROR, "Cannot create argument file", e)); //$NON-NLS-1$
+		}
+	}
+
+	String[] quoteForArgfile(String[] cmdLine) {
+		String[] quotedCmdLine = new String[cmdLine.length];
+		for (int i = 0; i < cmdLine.length; i++) {
+			String arg = cmdLine[i];
+			if (CommandLineQuoting.needsQuoting(arg)) {
+				StringBuilder escapedArg = new StringBuilder();
+				for (int j = 0; j < arg.length(); j++) {
+					char c = arg.charAt(j);
+					if (c == '\\') {
+						escapedArg.append('\\');
+					} else if (c == '\"') {
+						escapedArg.append('\\');
+					}
+					escapedArg.append(c);
+				}
+				arg = "\"" + escapedArg.toString() + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+			}
+			quotedCmdLine[i] = arg;
+		}
+		return quotedCmdLine;
+	}
+
+	protected String getLaunchConfigurationName() {
+		return launch.getLaunchConfiguration().getName();
+	}
+
+	protected String getLaunchTimeStamp() {
+		String timeStamp = launch.getAttribute(DebugPlugin.ATTR_LAUNCH_TIMESTAMP);
+		if (timeStamp == null) {
+			timeStamp = Long.toString(System.currentTimeMillis());
+		}
+		return timeStamp;
+	}
+
+	/**
+	 * The files that were created while shortening the path. They can be deleted once the process is terminated
+	 *
+	 * @return created files
+	 */
+	public List<File> getProcessTempFiles() {
+		return new ArrayList<>(processTempFiles);
+	}
+
+	public File getProcessTempFilesDir() {
+		return processTempFilesDir;
+	}
+
+	/**
+	 * @return the original, unshortened command line
+	 */
+	public String[] getOriginalCmdLine() {
+		return cmdLine;
+	}
+
+	/**
+	 * @return <code>true</code> if the JVM supports launching with argument files, <code>false</code> otherwise
+	 */
+	protected boolean isArgumentFileSupported() {
+		return JavaCore.compareJavaVersions(javaVersion, JavaCore.VERSION_9) >= 0;
+	}
+
+	/**
+	 * The directory to use to create temp files needed when shortening the classpath. By default, the working directory is used
+	 *
+	 * The java.io.tmpdir should not be used on MacOs (does not work for classpath-only jars)
+	 *
+	 * @param processTempFilesDir
+	 */
+	public void setProcessTempFilesDir(File processTempFilesDir) {
+		this.processTempFilesDir = processTempFilesDir;
+	}
+
+	/**
+	 * Writes the command line into an arguments file and returns the shortened command line.
+	 *
+	 * @return a shortened command line
+	 * @throws CoreException
+	 */
+	public String[] shortenCommandLine() throws CoreException {
+		List<String> fullCommandLine = new ArrayList<>(Arrays.asList(cmdLine));
+		List<String> shortCommandLine = new ArrayList<>();
+
+		shortCommandLine.add(fullCommandLine.remove(0));
+
+		File argumentFile = createArgumentFile(fullCommandLine.toArray(new String[fullCommandLine.size()]));
+		addProcessTempFile(argumentFile);
+		shortCommandLine.add("@" + argumentFile.getAbsolutePath());//$NON-NLS-1$
+
+		return shortCommandLine.toArray(new String[shortCommandLine.size()]);
+	}
+
+	/**
+	 * Indicates if the command line {@link #isArgumentFileSupported() can} and should be shortened.
+	 * <p>
+	 * The command line should only be shortened if at least Java 9 is used and the launch is configured to do so.
+	 * </p>
+	 *
+	 * @return <code>true</code> if {@link #isArgumentFileSupported()} returns <code>true</code> and command line should be shortened,
+	 *         <code>false</code> otherwise
+	 * @throws CoreException
+	 */
+	public boolean shouldShortenCommandLine() throws CoreException {
+		if (!isArgumentFileSupported()) {
+			return false;
+		}
+
+		if (cmdLine.length < 2) {
+			// no need to shorten if it's just the program argument
+			return false;
+		}
+
+		ILaunchConfiguration configuration = launch.getLaunchConfiguration();
+		if (configuration != null) {
+			return configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_USE_ARGFILE, false);
+		}
+
+		return false;
+	}
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
index e07c27b..f75790e 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMDebugger.java
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IPath;
@@ -283,11 +284,17 @@
 		}
 		subMonitor.worked(1);
 
+		CommandLineShortener commandLineShortener = new CommandLineShortener(fVMInstance, launch, newCmdLine, workingDir);
+		if (commandLineShortener.shouldShortenCommandLine()) {
+			cmdLine = commandLineShortener.shortenCommandLine();
+		}
+
 		CommandDetails cmd = new CommandDetails();
 		cmd.setCommandLine(cmdLine);
 		cmd.setEnvp(envp);
 		cmd.setWorkingDir(workingDir);
 		cmd.setClasspathShortener(classpathShortener);
+		cmd.setCommandLineShortener(commandLineShortener);
 		cmd.setPort(port);
 		return cmd;
 
@@ -359,8 +366,9 @@
 					}
 					process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
 				}
-				if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()) {
-					String tempFiles = cmdDetails.getClasspathShortener().getProcessTempFiles().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator));
+				if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()
+						|| !cmdDetails.getCommandLineShortener().getProcessTempFiles().isEmpty()) {
+					String tempFiles = Stream.concat(cmdDetails.getClasspathShortener().getProcessTempFiles().stream(), cmdDetails.getCommandLineShortener().getProcessTempFiles().stream()).map(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/internal/launching/StandardVMRunner.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/StandardVMRunner.java
index f89b4c7..fea720b 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
@@ -13,7 +13,6 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.launching;
 
-
 import java.io.File;
 import java.io.IOException;
 import java.text.DateFormat;
@@ -25,6 +24,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -43,7 +43,6 @@
 import org.eclipse.jdt.launching.VMRunnerConfiguration;
 import org.eclipse.osgi.util.NLS;
 
-
 /**
  * A launcher for running Java main classes.
  */
@@ -351,6 +350,7 @@
 		private String[] envp;
 		private File workingDir;
 		private ClasspathShortener classpathShortener;
+		private CommandLineShortener commandLineShortener;
 		private int port;
 
 		public String[] getEnvp() {
@@ -385,6 +385,14 @@
 			this.classpathShortener = classpathShortener;
 		}
 
+		public CommandLineShortener getCommandLineShortener() {
+			return commandLineShortener;
+		}
+
+		public void setCommandLineShortener(CommandLineShortener commandLineShortener) {
+			this.commandLineShortener = commandLineShortener;
+		}
+
 		public int getPort() {
 			return port;
 		}
@@ -477,11 +485,16 @@
 		if (newCmdLine != null) {
 			cmdLine = newCmdLine;
 		}
+		CommandLineShortener commandLineShortener = new CommandLineShortener(fVMInstance, launch, newCmdLine, workingDir);
+		if (commandLineShortener.shouldShortenCommandLine()) {
+			cmdLine = commandLineShortener.shortenCommandLine();
+		}
 		CommandDetails cmd = new CommandDetails();
 		cmd.setCommandLine(cmdLine);
 		cmd.setEnvp(envp);
 		cmd.setWorkingDir(workingDir);
 		cmd.setClasspathShortener(classpathShortener);
+		cmd.setCommandLineShortener(commandLineShortener);
 		subMonitor.worked(1);
 		return cmd;
 	}
@@ -531,8 +544,9 @@
 			}
 			process.setAttribute(DebugPlugin.ATTR_ENVIRONMENT, buff.toString());
 		}
-		if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()) {
-			String tempFiles = cmdDetails.getClasspathShortener().getProcessTempFiles().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator));
+		if (!cmdDetails.getClasspathShortener().getProcessTempFiles().isEmpty()
+				|| !cmdDetails.getCommandLineShortener().getProcessTempFiles().isEmpty()) {
+			String tempFiles = Stream.concat(cmdDetails.getClasspathShortener().getProcessTempFiles().stream(), cmdDetails.getCommandLineShortener().getProcessTempFiles().stream()).map(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 e6ef2e6..5ce14ef 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
@@ -21,7 +21,6 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.variables.IStringVariableManager;
 import org.eclipse.core.variables.VariablesPlugin;
@@ -29,9 +28,9 @@
 import org.eclipse.debug.core.ILaunch;
 import org.eclipse.debug.core.model.IProcess;
 import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.launching.CommandLineQuoting;
 import org.eclipse.jdt.internal.launching.LaunchingMessages;
 import org.eclipse.jdt.internal.launching.LaunchingPlugin;
-import org.eclipse.osgi.service.environment.Constants;
 
 /**
  * Abstract implementation of a VM runner.
@@ -121,46 +120,7 @@
 	 * @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];
-			if(cmdLine.length > 0) {
-				winCmdLine[0] = cmdLine[0];
-			}
-			for (int i = 1; i < cmdLine.length; i++) {
-				winCmdLine[i] = winQuote(cmdLine[i]);
-			}
-			cmdLine = winCmdLine;
-		}
-		return cmdLine;
-	}
-
-
-	private static boolean needsQuoting(String s) {
-		int len = s.length();
-		if (len == 0) {
-			return true;
-		}
-		if ("\"\"".equals(s)) //$NON-NLS-1$
-		 {
-			return false; // empty quotes must not be quoted again
-		}
-		for (int i = 0; i < len; i++) {
-			switch (s.charAt(i)) {
-				case ' ': case '\t': case '\\': case '"':
-					return true;
-			}
-		}
-		return false;
-	}
-
-	private static String winQuote(String s) {
-		if (! needsQuoting(s)) {
-			return s;
-		}
-		s = s.replaceAll("([\\\\]*)\"", "$1$1\\\\\""); //$NON-NLS-1$ //$NON-NLS-2$
-		s = s.replaceAll("([\\\\]*)\\z", "$1$1"); //$NON-NLS-1$ //$NON-NLS-2$
-		return "\"" + s + "\""; //$NON-NLS-1$ //$NON-NLS-2$
+		return CommandLineQuoting.quoteWindowsArgs(cmdLine);
 	}
 
 	/**
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
index ff147bb..4b7035e 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IJavaLaunchConfigurationConstants.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -379,6 +379,15 @@
 	 */
 	public static final String ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES = LaunchingPlugin.getUniqueIdentifier()
 			+ ".ATTR_SHOW_CODEDETAILS_IN_EXCEPTION_MESSAGES"; //$NON-NLS-1$
+
+	/**
+	 * Attribute key to write arguments into an file when launching allowing an unlimited number of arguments. Only valid for java > 8 see
+	 * https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8027634
+	 *
+	 * @since 3.19
+	 */
+	public static final String ATTR_USE_ARGFILE = LaunchingPlugin.getUniqueIdentifier() + ".ATTR_ATTR_USE_ARGFILE"; //$NON-NLS-1$
+
 	/**
 	 * Launch configuration attribute key. The value is a boolean specifying whether output folders corresponding to test sources should not be added
 	 * to the runtime classpath and test dependencies should not be added to the default classpath.
diff --git a/org.eclipse.jdt.launching/pom.xml b/org.eclipse.jdt.launching/pom.xml
index 9db056b..ed03b9f 100644
--- a/org.eclipse.jdt.launching/pom.xml
+++ b/org.eclipse.jdt.launching/pom.xml
@@ -18,7 +18,7 @@
   </parent>
   <groupId>org.eclipse.jdt</groupId>
   <artifactId>org.eclipse.jdt.launching</artifactId>
-  <version>3.18.100-SNAPSHOT</version>
+  <version>3.19.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
   
   <build>