Bug 529772 - [9] Cleanup and improve JavaRuntime.getModuleCLIOptions

Change-Id: I337c0fddf6b94bb371384f3c9b0598fcdd9567df
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 324ded1..7ab213a 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
@@ -23,6 +23,8 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -66,7 +68,6 @@
 import org.eclipse.jdt.core.IPackageFragmentRoot;
 import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.core.provisional.JavaModelAccess;
 import org.eclipse.jdt.internal.core.JavaProject;
 import org.eclipse.jdt.internal.launching.CompositeId;
 import org.eclipse.jdt.internal.launching.DefaultEntryResolver;
@@ -3366,7 +3367,11 @@
 		throw new CoreException(status);
 	}
 
-	private static String PATCH_MODULE = "--" + IClasspathAttribute.PATCH_MODULE; //$NON-NLS-1$
+	private static final String BLANK = " "; //$NON-NLS-1$
+	private static final String COMMA = ","; //$NON-NLS-1$
+	private static final String OPTION_START = "--"; //$NON-NLS-1$
+	private static final String ADD_MODULES = "--add-modules "; //$NON-NLS-1$
+	private static final String LIMIT_MODULES = "--limit-modules "; //$NON-NLS-1$
 
 	/**
 	 * Returns the module-related command line options for the configuration that are needed at runtime as equivalents of those options specified by
@@ -3396,58 +3401,11 @@
 					for (IClasspathEntry iClasspathEntry : rawClasspath) {
 						if (iClasspathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
 							if (JavaRuntime.JRE_CONTAINER.equals(iClasspathEntry.getPath().segment(0))) {
-								String cliOptions = JavaModelAccess.getModuleCLIOptions(jp, iClasspathEntry);
-								String str11 = cliOptions;
-								int index = cliOptions.indexOf(":"); //$NON-NLS-1$
-								StringBuffer cliOptionBuffer = new StringBuffer();
-								if ( index != -1) {
-									String[] splited = str11.split("\\s+"); //$NON-NLS-1$
-									int i = 0;
-									for (String string : splited) {
-										if (string.equals(PATCH_MODULE)) {
-											cliOptionBuffer.append(" "); //$NON-NLS-1$
-											cliOptionBuffer.append(string);
-											i++;
-											continue;
-										}
-										if (i > 0) {
-											if (splited[i - 1].equals(PATCH_MODULE)) {
-												cliOptionBuffer.append(" "); //$NON-NLS-1$
-												cliOptionBuffer.append(string);
-												i++;
-												continue;
-											}
-										}
-										index = string.indexOf(":"); //$NON-NLS-1$
-										if (index == -1) {
-											if (i > 0) {
-												cliOptionBuffer.append(" "); //$NON-NLS-1$
-											}
-											cliOptionBuffer.append(string);
-
-										} else {
-											String[] splited1 = string.split(":"); //$NON-NLS-1$
-											int j = 0;
-											for (String string2 : splited1) {
-												if (j > 0) {
-													cliOptionBuffer.append(" "); //$NON-NLS-1$
-													cliOptionBuffer.append(splited[i - 1]);
-												}
-												cliOptionBuffer.append(" "); //$NON-NLS-1$
-												cliOptionBuffer.append(string2);
-												j++;
-											}
-										}
-										i++;
-									}
-								}
-								else {
-									cliOptionBuffer.append(cliOptions);
-								}
-								if (cliOptionString.length() > 0 && cliOptionBuffer.length() > 0) {
+								String cliOptions = getModuleCLIOptions(jp, iClasspathEntry);
+								if (cliOptionString.length() > 0 && cliOptions.length() > 0) {
 									cliOptionString.append(" "); //$NON-NLS-1$
 								}
-								cliOptionString.append(cliOptionBuffer);
+								cliOptionString.append(cliOptions);
 							}
 						}
 					}
@@ -3460,4 +3418,79 @@
 		}
 		return cliOptionString.toString().trim();
 	}
+
+	/**
+	 * Returns the module-related command line options that are needed at runtime as equivalents of those options specified by
+	 * {@link IClasspathAttribute}s of the following names:
+	 * <ul>
+	 * <li>{@link IClasspathAttribute#ADD_EXPORTS}</li>
+	 * <li>{@link IClasspathAttribute#ADD_READS}</li>
+	 * <li>{@link IClasspathAttribute#LIMIT_MODULES}</li>
+	 * </ul>
+	 * <p>
+	 * Note that the {@link IClasspathAttribute#LIMIT_MODULES} value may be split into an {@code --add-modules} part and a {@code --limit-modules}
+	 * part.
+	 * </p>
+	 *
+	 * @param project
+	 *            the project holding the main class to be launched
+	 * @param systemLibrary
+	 *            the classpath entry of the given project which represents the JRE System Library
+	 * @return module-related command line options suitable for running the application.
+	 * @throws JavaModelException
+	 *             when access to the classpath or module description of the given project fails.
+	 */
+	private static String getModuleCLIOptions(IJavaProject project, IClasspathEntry systemLibrary) throws JavaModelException {
+		StringBuilder buf = new StringBuilder();
+		for (IClasspathEntry classpathEntry : project.getRawClasspath()) {
+			for (IClasspathAttribute classpathAttribute : classpathEntry.getExtraAttributes()) {
+				String optName = classpathAttribute.getName();
+				switch (optName) {
+					case IClasspathAttribute.ADD_EXPORTS:
+					case IClasspathAttribute.ADD_READS:
+						for (String value : classpathAttribute.getValue().split(COMMA)) {
+							buf.append(OPTION_START).append(optName).append(BLANK).append(value).append(BLANK);
+						}
+						break;
+					case IClasspathAttribute.LIMIT_MODULES:
+						addLimitModules(buf, project, systemLibrary, classpathAttribute.getValue());
+						break;
+				}
+			}
+		}
+		return buf.toString().trim();
+	}
+
+	private static void addLimitModules(StringBuilder buf, IJavaProject prj, IClasspathEntry systemLibrary, String value) throws JavaModelException {
+		String[] modules = value.split(COMMA);
+		boolean isUnnamed = prj.getModuleDescription() == null;
+		if (isUnnamed) {
+			Set<String> selected = new HashSet<>(Arrays.asList(modules));
+			List<IPackageFragmentRoot> allSystemRoots = Arrays.asList(prj.findUnfilteredPackageFragmentRoots(systemLibrary));
+			Set<String> defaultModules = new HashSet<>(JavaProject.defaultRootModules(allSystemRoots));
+
+			Set<String> limit = new HashSet<>(defaultModules);
+			if (limit.retainAll(selected)) { // limit = selected ∩ default -- only add the option, if limit ⊂ default
+				if (limit.isEmpty()) {
+					throw new IllegalArgumentException("Cannot hide all modules, at least java.base is required"); //$NON-NLS-1$
+				}
+				buf.append(LIMIT_MODULES).append(joinedSortedList(limit)).append(BLANK);
+			}
+
+			Set<String> add = new HashSet<>(selected);
+			add.removeAll(defaultModules);
+			if (!add.isEmpty()) { // add = selected \ default
+				buf.append(ADD_MODULES).append(joinedSortedList(add)).append(BLANK);
+			}
+		} else {
+			Arrays.sort(modules);
+			buf.append(LIMIT_MODULES).append(String.join(COMMA, modules)).append(BLANK);
+		}
+	}
+
+	private static String joinedSortedList(Collection<String> list) {
+		String[] limitArray = list.toArray(new String[list.size()]);
+		Arrays.sort(limitArray);
+		return String.join(COMMA, limitArray);
+	}
 }