Bug 534884 - [9] Transitive project dependencies are missing when
launching on a modular JVM

Change-Id: I3aad368f8fa759d58b436b33db27129567f9e261
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
index 8dc2675..61d2ed5 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
@@ -11,6 +11,7 @@
 package org.eclipse.jdt.internal.launching;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
@@ -22,6 +23,7 @@
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.debug.core.ILaunchConfiguration;
 import org.eclipse.jdt.core.ClasspathContainerInitializer;
+import org.eclipse.jdt.core.IClasspathAttribute;
 import org.eclipse.jdt.core.IClasspathContainer;
 import org.eclipse.jdt.core.IClasspathEntry;
 import org.eclipse.jdt.core.IJavaProject;
@@ -152,7 +154,7 @@
 		IClasspathEntry entry = JavaCore.newProjectEntry(getJavaProject().getProject().getFullPath());
 		List<Object> classpathEntries = new ArrayList<>(5);
 		List<IClasspathEntry> expanding = new ArrayList<>(5);
-		expandProject(entry, classpathEntries, expanding, excludeTestCode);
+		expandProject(entry, classpathEntries, expanding, excludeTestCode, isExportedEntriesOnly(), getJavaProject(), false);
 		IRuntimeClasspathEntry[] runtimeEntries = new IRuntimeClasspathEntry[classpathEntries.size()];
 		for (int i = 0; i < runtimeEntries.length; i++) {
 			Object e = classpathEntries.get(i);
@@ -184,10 +186,16 @@
 	 *            a list of projects that have been or are currently being expanded (to detect cycles)
 	 * @param excludeTestCode
 	 *            if true, test dependencies will be excluded
+	 * @param exportedEntriesOnly
+	 *            if true, only add exported transitive dependencies
+	 * @param rootProject
+	 *            the root project for which the classpath is computed
+	 * @param isModularJVM
+	 *            if jvm is java 9 or later
 	 * @exception CoreException
 	 *                if unable to expand the classpath
 	 */
-	private void expandProject(IClasspathEntry projectEntry, List<Object> expandedPath, List<IClasspathEntry> expanding, boolean excludeTestCode) throws CoreException {
+	public static void expandProject(IClasspathEntry projectEntry, List<Object> expandedPath, List<IClasspathEntry> expanding, boolean excludeTestCode, boolean exportedEntriesOnly, IJavaProject rootProject, boolean isModularJVM) throws CoreException {
 		expanding.add(projectEntry);
 		// 1. Get the raw classpath
 		// 2. Replace source folder entries with a project entry
@@ -222,7 +230,7 @@
 				// add exported entries, as configured
 				if (classpathEntry.isExported()) {
 					unexpandedPath.add(classpathEntry);
-				} else if (!isExportedEntriesOnly() || project.equals(getJavaProject())) {
+				} else if (!exportedEntriesOnly || project.equals(rootProject)) {
 					// add non exported entries from root project or if we are including all entries
 					unexpandedPath.add(classpathEntry);
 				}
@@ -239,7 +247,7 @@
 				switch (entry.getEntryKind()) {
 					case IClasspathEntry.CPE_PROJECT:
 						if (!expanding.contains(entry)) {
-							expandProject(entry, expandedPath, expanding, excludeTestCode);
+							expandProject(entry, expandedPath, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
 						}
 						break;
 					case IClasspathEntry.CPE_CONTAINER:
@@ -248,7 +256,16 @@
 						if (container != null) {
 							switch (container.getKind()) {
 								case IClasspathContainer.K_APPLICATION:
-									property = IRuntimeClasspathEntry.USER_CLASSES;
+									if (isModularJVM) {
+										if (Arrays.stream(entry.getExtraAttributes()).anyMatch(attribute -> IClasspathAttribute.MODULE.equals(attribute.getName())
+												&& Boolean.TRUE.toString().equals(attribute.getValue()))) {
+											property = IRuntimeClasspathEntry.MODULE_PATH;
+										} else {
+											property = IRuntimeClasspathEntry.CLASS_PATH;
+										}
+									} else {
+										property = IRuntimeClasspathEntry.USER_CLASSES;
+									}
 									break;
 								case IClasspathContainer.K_DEFAULT_SYSTEM:
 									property = IRuntimeClasspathEntry.STANDARD_CLASSES;
@@ -305,6 +322,9 @@
 						break;
 					case IClasspathEntry.CPE_VARIABLE:
 						IRuntimeClasspathEntry r = JavaRuntime.newVariableRuntimeClasspathEntry(entry.getPath());
+						if (isModularJVM) {
+							adjustClasspathProperty(r, entry);
+						}
 						r.setSourceAttachmentPath(entry.getSourceAttachmentPath());
 						r.setSourceAttachmentRootPath(entry.getSourceAttachmentRootPath());
 						if (!expandedPath.contains(r)) {
@@ -319,6 +339,9 @@
 								for (int i = 0; i < roots.length; i++) {
 									IPackageFragmentRoot root = roots[i];
 									r = JavaRuntime.newArchiveRuntimeClasspathEntry(root.getPath(), entry.getSourceAttachmentPath(), entry.getSourceAttachmentRootPath(), entry.getAccessRules(), entry.getExtraAttributes(), entry.isExported());
+									if (isModularJVM) {
+										adjustClasspathProperty(r, entry);
+									}
 									r.setSourceAttachmentPath(entry.getSourceAttachmentPath());
 									r.setSourceAttachmentRootPath(entry.getSourceAttachmentRootPath());
 									if (!expandedPath.contains(r)) {
@@ -335,6 +358,18 @@
 		}
 		return;
 	}
+
+	public static void adjustClasspathProperty(IRuntimeClasspathEntry r, IClasspathEntry entry) {
+		if (r.getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES) {
+			if (Arrays.stream(entry.getExtraAttributes()).anyMatch(attribute -> IClasspathAttribute.MODULE.equals(attribute.getName())
+					&& Boolean.TRUE.toString().equals(attribute.getValue()))) {
+				r.setClasspathProperty(IRuntimeClasspathEntry.MODULE_PATH);
+			} else {
+				r.setClasspathProperty(IRuntimeClasspathEntry.CLASS_PATH);
+			}
+		}
+	}
+	
 	/* (non-Javadoc)
 	 * @see org.eclipse.jdt.launching.IRuntimeClasspathEntry2#isComposite()
 	 */
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 588c221..fc5c259 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
@@ -979,87 +979,42 @@
 	 * @since 3.10
 	 */
 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeDependencies(IJavaProject project, boolean excludeTestCode) throws CoreException {
-		List<IRuntimeClasspathEntry> classpathEntries = new ArrayList<>(3);
-		if (!(project instanceof JavaProject)) {
-			return classpathEntries.toArray(new IRuntimeClasspathEntry[classpathEntries.size()]);
-		}
-		JavaProject javaProject = (JavaProject) project;
-		IClasspathEntry[] entries = javaProject.getExpandedClasspath(excludeTestCode);
-
 		IClasspathEntry entry1 = JavaCore.newProjectEntry(project.getProject().getFullPath());
-		if (isModularProject(project)) {
-			classpathEntries.add(new RuntimeClasspathEntry(entry1, IRuntimeClasspathEntry.MODULE_PATH));
-		} else {
-			classpathEntries.add(new RuntimeClasspathEntry(entry1, IRuntimeClasspathEntry.CLASS_PATH));
-		}
-		for (int i = 0; i < entries.length; i++) {
-			IClasspathEntry entry = entries[i];
-			switch (entry.getEntryKind()) {
-				case IClasspathEntry.CPE_CONTAINER:
-					IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
-					if (container != null) {
-						switch (container.getKind()) {
-							case IClasspathContainer.K_APPLICATION:
-								// don't look at application entries
-								break;
-							case IClasspathContainer.K_DEFAULT_SYSTEM:
-								if (isModule(entry, project)) {
-									classpathEntries.add(newRuntimeContainerClasspathEntry(container.getPath(), IRuntimeClasspathEntry.MODULE_PATH, project));
-								} else {
-									classpathEntries.add(newRuntimeContainerClasspathEntry(container.getPath(), IRuntimeClasspathEntry.CLASS_PATH, project));
-								}
-								break;
-							case IClasspathContainer.K_SYSTEM:
-								if (isModule(entry, project)) {
-									classpathEntries.add(newRuntimeContainerClasspathEntry(container.getPath(), IRuntimeClasspathEntry.MODULE_PATH, project));
-								} else {
-									classpathEntries.add(newRuntimeContainerClasspathEntry(container.getPath(), IRuntimeClasspathEntry.CLASS_PATH, project));
-								}
-								break;
-						}
-					}
-					break;
-				case IClasspathEntry.CPE_PROJECT:
-					String name = entry.getPath().lastSegment();
-					IProject dep = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
-					IJavaProject javaProject1 = JavaCore.create(dep);
-					if (isModule(entry, project)) {
-						classpathEntries.add(newProjectRuntimeClasspathEntry(javaProject1, IRuntimeClasspathEntry.MODULE_PATH));
+		List<Object> classpathEntries = new ArrayList<>(5);
+		List<IClasspathEntry> expanding = new ArrayList<>(5);
+		boolean exportedEntriesOnly = Platform.getPreferencesService().getBoolean(LaunchingPlugin.ID_PLUGIN, JavaRuntime.PREF_ONLY_INCLUDE_EXPORTED_CLASSPATH_ENTRIES, false, null);
+		DefaultProjectClasspathEntry.expandProject(entry1, classpathEntries, expanding, excludeTestCode, exportedEntriesOnly, project, true);
+		IRuntimeClasspathEntry[] runtimeEntries = new IRuntimeClasspathEntry[classpathEntries.size()];
+		for (int i = 0; i < runtimeEntries.length; i++) {
+			Object e = classpathEntries.get(i);
+			if (e instanceof IClasspathEntry) {
+				IClasspathEntry cpe = (IClasspathEntry) e;
+				if (cpe == entry1) {
+					if (isModularProject(project)) {
+						runtimeEntries[i] = new RuntimeClasspathEntry(entry1, IRuntimeClasspathEntry.MODULE_PATH);
 					} else {
-						classpathEntries.add(newProjectRuntimeClasspathEntry(javaProject1, IRuntimeClasspathEntry.CLASS_PATH));
+						runtimeEntries[i] = new RuntimeClasspathEntry(entry1, IRuntimeClasspathEntry.CLASS_PATH);
 					}
-					break;
-				case IClasspathEntry.CPE_VARIABLE:
-					if (JRELIB_VARIABLE.equals(entry.getPath().segment(0))) {
-						IRuntimeClasspathEntry jre = newVariableRuntimeClasspathEntry(entry.getPath());
-						jre.setClasspathProperty(IRuntimeClasspathEntry.MODULE_PATH);
-						classpathEntries.add(jre);
-					}
-					break;
-				case IClasspathEntry.CPE_LIBRARY:
-					IPackageFragmentRoot root = project.findPackageFragmentRoot(entry.getPath());
-					if (root != null && !root.getRawClasspathEntry().getPath().segment(0).contains("JRE_CONTAINER")) { //$NON-NLS-1$
-						IRuntimeClasspathEntry r;
-						if (JavaRuntime.isModule(entry, project)) {
-							r = JavaRuntime.newArchiveRuntimeClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.MODULE_PATH);
-						} else {
-							r = JavaRuntime.newArchiveRuntimeClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.CLASS_PATH);
-						}
-						r.setSourceAttachmentPath(entry.getSourceAttachmentPath());
-						r.setSourceAttachmentRootPath(entry.getSourceAttachmentRootPath());
-						classpathEntries.add(r);
-					}
-					break;
-				default:
-					break;
+				} else {
+					runtimeEntries[i] = new RuntimeClasspathEntry(cpe);
+					DefaultProjectClasspathEntry.adjustClasspathProperty(runtimeEntries[i], cpe);
+				}
+			} else {
+				runtimeEntries[i] = (IRuntimeClasspathEntry) e;
 			}
 		}
-
+		List<IRuntimeClasspathEntry> ordered = new ArrayList<>(runtimeEntries.length);
+		for (int i = 0; i < runtimeEntries.length; i++) {
+			if (runtimeEntries[i].getClasspathProperty() != IRuntimeClasspathEntry.STANDARD_CLASSES
+					&& runtimeEntries[i].getClasspathProperty() != IRuntimeClasspathEntry.BOOTSTRAP_CLASSES) {
+				ordered.add(runtimeEntries[i]);
+			}
+		}
 		IRuntimeClasspathEntry jreEntry = JavaRuntime.computeModularJREEntry(project);
 		if (jreEntry != null) { // With some jre stub jars don't have jre entries
-			classpathEntries.add(jreEntry);
+			ordered.add(jreEntry);
 		}
-		return classpathEntries.toArray(new IRuntimeClasspathEntry[classpathEntries.size()]);
+		return ordered.toArray(new IRuntimeClasspathEntry[ordered.size()]);
 	}
 
 	/**