Bug 527593 - [9] support launching modular projects with multiple output
folders

Change-Id: I12874713ed27a0cc17f1e7c06899cb6c0cf7c74d
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/OverrideDependenciesDialog.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/OverrideDependenciesDialog.java
index 5e9e94a..8ec39fe 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/OverrideDependenciesDialog.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/OverrideDependenciesDialog.java
@@ -10,11 +10,15 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.debug.ui.actions;
 
+import java.util.Set;
+
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.debug.core.ILaunchDelegate;
+import org.eclipse.debug.core.ILaunchManager;
+import org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate;
 import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
-import org.eclipse.jdt.launching.JavaRuntime;
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.graphics.Font;
@@ -71,7 +75,10 @@
 				fModuleArgumentsText.setText(str);
 
 			} else {
-				fModuleArgumentsText.setText(JavaRuntime.getModuleCLIOptions(flaunchConfiguration));
+				AbstractJavaLaunchConfigurationDelegate delegate = getJavaLaunchConfigurationDelegate();
+				if (delegate != null) {
+					fModuleArgumentsText.setText(delegate.getModuleCLIOptions(flaunchConfiguration));
+				}
 			}
 			fOriginalText = fModuleArgumentsText.getText();
 
@@ -82,6 +89,19 @@
 		return comp;
 	}
 
+	public AbstractJavaLaunchConfigurationDelegate getJavaLaunchConfigurationDelegate() throws CoreException {
+		Set<String> modes = flaunchConfiguration.getModes();
+		modes.add(ILaunchManager.RUN_MODE);
+		AbstractJavaLaunchConfigurationDelegate delegate = null;
+		for (ILaunchDelegate launchDelegate : flaunchConfiguration.getType().getDelegates(modes)) {
+			if (launchDelegate.getDelegate() instanceof AbstractJavaLaunchConfigurationDelegate) {
+				delegate = (AbstractJavaLaunchConfigurationDelegate) launchDelegate.getDelegate();
+				break;
+			}
+		}
+		return delegate;
+	}
+
 	/* (non-Javadoc)
 	 * @see org.eclipse.jface.dialogs.MessageDialog#buttonPressed(int)
 	 */
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractJavaLaunchConfigurationDelegate.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractJavaLaunchConfigurationDelegate.java
index 21b7d98..cefcb9a 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractJavaLaunchConfigurationDelegate.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/AbstractJavaLaunchConfigurationDelegate.java
@@ -18,8 +18,10 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import org.eclipse.core.resources.IContainer;
@@ -47,6 +49,7 @@
 import org.eclipse.jdt.core.IClasspathAttribute;
 import org.eclipse.jdt.core.IJavaModelMarker;
 import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IModuleDescription;
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.debug.core.IJavaDebugTarget;
 import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
@@ -428,7 +431,8 @@
 		for (IRuntimeClasspathEntry entry : entries) {
 			String location = entry.getLocation();
 			if (location != null) {
-				if (entry.getClasspathProperty() != IRuntimeClasspathEntry.MODULE_PATH) {
+				if (entry.getClasspathProperty() != IRuntimeClasspathEntry.MODULE_PATH
+						&& entry.getClasspathProperty() != IRuntimeClasspathEntry.PATCH_MODULE) {
 					if (!set.contains(location)) {
 						userEntries.add(location);
 						set.add(location);
@@ -1117,9 +1121,53 @@
 	 * <li>{@link IClasspathAttribute#PATCH_MODULE}</li>
 	 * </ul>
 	 *
+	 * @throws CoreException
+	 *
 	 * @since 3.10
 	 */
-	protected String getModuleCLIOptions(ILaunchConfiguration configuration) {
-		return JavaRuntime.getModuleCLIOptions(configuration);
+	public String getModuleCLIOptions(ILaunchConfiguration configuration) throws CoreException {
+		String moduleCLIOptions = JavaRuntime.getModuleCLIOptions(configuration);
+
+		IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedRuntimeClasspath(configuration);
+		entries = JavaRuntime.resolveRuntimeClasspath(entries, configuration);
+		LinkedHashMap<String, String> moduleToLocations = new LinkedHashMap<>();
+
+		for (IRuntimeClasspathEntry entry : entries) {
+			String location = entry.getLocation();
+			if (location != null) {
+				if (entry.getClasspathProperty() == IRuntimeClasspathEntry.PATCH_MODULE) {
+					IJavaProject javaProject = entry.getJavaProject();
+					IModuleDescription moduleDescription = javaProject == null ? null : javaProject.getModuleDescription();
+					if (moduleDescription != null) {
+						String moduleName = moduleDescription.getElementName();
+						String locations = moduleToLocations.get(moduleName);
+						if (locations == null) {
+							moduleToLocations.put(moduleName, location);
+						} else {
+							moduleToLocations.put(moduleName, locations + File.pathSeparator + location);
+						}
+					} else {
+						// should not happen, log?
+					}
+				}
+			}
+		}
+		if (moduleToLocations.isEmpty()) {
+			return moduleCLIOptions;
+		}
+		StringBuilder sb = new StringBuilder(moduleCLIOptions);
+
+		for (Entry<String, String> entry : moduleToLocations.entrySet()) {
+			if (sb.length() > 0) {
+				sb.append(' ');
+			}
+			sb.append("--patch-module"); //$NON-NLS-1$
+			sb.append(' ');
+			sb.append(entry.getKey());
+			sb.append('=');
+			sb.append(entry.getValue());
+		}
+
+		return sb.toString();
 	}
 }
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry.java
index 265c19d..64e0aa1 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/IRuntimeClasspathEntry.java
@@ -97,19 +97,26 @@
 
 	/**
 	 * Classpath property identifier for entries that should appear on the module path for modular project.
-	 * 
+	 *
 	 * @since 3.10
 	 */
 	public static final int MODULE_PATH = 4;
 
 	/**
 	 * Classpath property identifier for entries that should appear on the class path for modular project.
-	 * 
+	 *
 	 * @since 3.10
 	 */
 	public static final int CLASS_PATH = 5;
 
 	/**
+	 * Classpath property identifier for entries that should appear as --patch-module argument for a modular project.
+	 *
+	 * @since 3.10
+	 */
+	public static final int PATCH_MODULE = 6;
+
+	/**
 	 * Returns this classpath entry's type. The type of a runtime classpath entry is
 	 * identified by one of the following constants:
 	 * <ul>
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java
index ec76f9c..8dab81c 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/JavaRuntime.java
@@ -1317,7 +1317,9 @@
 				}
 			}
 		}
-		if (nonDefault.isEmpty()) {
+		boolean isModular = project.getModuleDescription() != null;
+		if (nonDefault.isEmpty() && !isModular) {
+			// return here only if non-modular, because patch-module might be needed otherwise
 			return null;
 		}
 		// add the default location if not already included
@@ -1329,11 +1331,20 @@
 		for (int i = 0; i < locations.length; i++) {
 			IClasspathEntry newEntry = JavaCore.newLibraryEntry(nonDefault.get(i), null, null);
 			locations[i] = new RuntimeClasspathEntry(newEntry);
-			locations[i].setClasspathProperty(classpathProperty);
+			if (isModular && !containsModuleInfo(locations[i])) {
+				locations[i].setClasspathProperty(IRuntimeClasspathEntry.PATCH_MODULE);
+				((RuntimeClasspathEntry) locations[i]).setJavaProject(project);
+			} else {
+				locations[i].setClasspathProperty(classpathProperty);
+			}
 		}
 		return locations;
 	}
 
+	private static boolean containsModuleInfo(IRuntimeClasspathEntry entry) {
+		return new File(entry.getLocation() + File.separator + "module-info.class").exists(); //$NON-NLS-1$
+	}
+
 	/**
 	 * Returns resolved entries for the given entry in the context of the given
 	 * Java project. If the entry is of kind