Bug 573521  - Merge remote-tracking branch 'origin/master' into BETA_JAVA17

Change-Id: Ieaf05de3b9295ccf28011824319776e0d141e036
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java
index 89d20be..f21e89c 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugHoverTests.java
@@ -912,6 +912,48 @@
 		}
 	}
 
+	public void testBug574969_onChainHover_preserveEditorSelection() throws Exception {
+		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
+
+		final String typeName = "Bug572629";
+		final String expectedMethod = "main";
+		final int frameNumber = 1;
+		final int bpLine = 64;
+
+		IJavaBreakpoint bp = createLineBreakpoint(bpLine, "", typeName + ".java", typeName);
+		bp.setSuspendPolicy(IJavaBreakpoint.SUSPEND_THREAD);
+		IFile file = (IFile) bp.getMarker().getResource();
+		assertEquals(typeName + ".java", file.getName());
+
+		IJavaThread thread = null;
+		try {
+			thread = launchToBreakpoint(typeName);
+			CompilationUnitEditor part = openEditorAndValidateStack(expectedMethod, frameNumber, file, thread);
+
+			sync(() -> part.selectAndReveal(part.getViewer().getDocument().get().lastIndexOf("local.names"), "local.names".length()));
+
+			JavaDebugHover hover = new JavaDebugHover();
+			hover.setEditor(part);
+
+			int offset = part.getViewer().getDocument().get().lastIndexOf("local.names.length") + "local.names.".length();
+			IRegion region = new Region(offset, "length".length());
+			IVariable info = (IVariable) sync(() -> hover.getHoverInfo2(part.getViewer(), region));
+
+			assertNotNull(info);
+			assertEquals("local.names.length", info.getName());
+			assertEquals("1", info.getValue().getValueString());
+
+			ITextSelection selection = sync(() -> {
+				processUiEvents(100);
+				return (ITextSelection) part.getSelectionProvider().getSelection();
+			});
+			assertEquals(selection.getText(), "local.names");
+		} finally {
+			terminateAndRemove(thread);
+			removeAllBreakpoints();
+		}
+	}
+
 	private CompilationUnitEditor openEditorAndValidateStack(final String expectedMethod, final int expectedFramesNumber, IFile file, IJavaThread thread) throws Exception, DebugException {
 		// Let now all pending jobs proceed, ignore console jobs
 		sync(() -> TestUtil.waitForJobs(getName(), 1000, 10000, ProcessConsole.class));
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java
index 4076ce1..32ccf16 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaDebugHover.java
@@ -566,7 +566,7 @@
 
 			public void run() throws DebugException {
 				IAstEvaluationEngine engine = JDIDebugPlugin.getDefault().getEvaluationEngine(project, (IJavaDebugTarget) frame.getDebugTarget());
-				engine.evaluate(snippet, frame, this, DebugEvent.EVALUATION, false);
+				engine.evaluate(snippet, frame, this, DebugEvent.EVALUATION_IMPLICIT, false);
 			}
 
 			public Optional<IEvaluationResult> getResult() {
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 d2b45ab..8002e80 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
@@ -15,8 +15,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
@@ -199,6 +201,11 @@
 	 *                if unable to expand the classpath
 	 */
 	public static void expandProject(IClasspathEntry projectEntry, List<Object> expandedPath, List<IClasspathEntry> expanding, boolean excludeTestCode, boolean exportedEntriesOnly, IJavaProject rootProject, boolean isModularJVM) throws CoreException {
+		final Set<Object> visitedEntries = new HashSet<>();
+		expandProjectInternal(projectEntry, expandedPath, visitedEntries, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
+	}
+
+	public static void expandProjectInternal(IClasspathEntry projectEntry, List<Object> expandedPath, Set<Object> visitedEntries, 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
@@ -250,7 +257,7 @@
 				switch (entry.getEntryKind()) {
 					case IClasspathEntry.CPE_PROJECT:
 						if (!expanding.contains(entry)) {
-							expandProject(entry, expandedPath, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
+							expandProjectInternal(entry, expandedPath, visitedEntries, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
 						}
 						break;
 					case IClasspathEntry.CPE_CONTAINER:
@@ -338,17 +345,20 @@
 						if (!expandedPath.contains(entry)) {
 							// resolve project relative paths - @see bug 57732 & bug 248466
 							if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
-								IPackageFragmentRoot[] roots = project.findPackageFragmentRoots(entry);
-								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)) {
-										expandedPath.add(r);
+								if (!visitedEntries.contains(entry)) {
+									visitedEntries.add(entry);
+									IPackageFragmentRoot[] roots = project.findPackageFragmentRoots(entry);
+									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)) {
+											expandedPath.add(r);
+										}
 									}
 								}
 							} else {
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 6caecc9..b9f2d8a 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
@@ -1663,41 +1663,59 @@
 	 * @since 2.0
 	 */
 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException {
-		if (isModularConfiguration(configuration)) {
-			IRuntimeClasspathEntry[] entries1 = getClasspathProvider(configuration).resolveClasspath(entries, configuration);
-			ArrayList<IRuntimeClasspathEntry> entries2 = new ArrayList<>(entries1.length);
+		if (!isModularConfiguration(configuration)) {
+			return getClasspathProvider(configuration).resolveClasspath(entries, configuration);
+		}
+		IRuntimeClasspathEntry[] entries1 = getClasspathProvider(configuration).resolveClasspath(entries, configuration);
+		List<IRuntimeClasspathEntry> entries2 = new ArrayList<>(entries1.length);
+		IJavaProject project;
+		try {
+			project = JavaRuntime.getJavaProject(configuration);
+		} catch (CoreException e) {
+			project = null;
+		}
+		if (project == null) {
+			entries2.addAll(Arrays.asList(entries1));
+		} else {
+			// Add all entries except the one from JRE itself
+			IPackageFragmentRoot jreContainer = findJreContainer(project);
+			IPath canonicalJrePath = jreContainer != null ? JavaProject.canonicalizedPath(jreContainer.getPath()) : null;
+
 			for (IRuntimeClasspathEntry entry : entries1) {
 				switch (entry.getClasspathEntry().getEntryKind()) {
 					case IClasspathEntry.CPE_LIBRARY:
-						try {
-							IJavaProject project = JavaRuntime.getJavaProject(configuration);
-							if (project == null) {
-								entries2.add(entry);
-							}
-							else {
-								IPackageFragmentRoot root = project.findPackageFragmentRoot(entry.getPath());
-								if (root == null && !entry.getPath().lastSegment().contains("jrt-fs.jar")) { //$NON-NLS-1$
-									entries2.add(entry);
-								} else if (root != null && !root.getRawClasspathEntry().getPath().segment(0).contains("JRE_CONTAINER")) { //$NON-NLS-1$
-									entries2.add(entry);
-								}
-							}
-						}
-						catch (CoreException ex) {
-							// Not a java project
-							if (!entry.getPath().lastSegment().contains("jrt-fs.jar")) { //$NON-NLS-1$
-								entries2.add(entry);
-							}
+						if (!entry.getPath().lastSegment().contains("jrt-fs.jar") //$NON-NLS-1$
+								&& (canonicalJrePath == null || !canonicalJrePath.equals(JavaProject.canonicalizedPath(entry.getPath())))) {
+							entries2.add(entry);
 						}
 						break;
 					default:
 						entries2.add(entry);
-
 				}
 			}
-			return entries2.toArray(new IRuntimeClasspathEntry[entries2.size()]);
 		}
-		return getClasspathProvider(configuration).resolveClasspath(entries, configuration);
+		return entries2.toArray(new IRuntimeClasspathEntry[entries2.size()]);
+	}
+
+	/**
+	 * Find the {@link IPackageFragmentRoot} of the JRE container for the given project, if it exists.
+	 *
+	 * @param project
+	 *            Java project
+	 * @return the {@link IPackageFragmentRoot} for the JRE container or null if no JRE container is on the classpath
+	 * @throws JavaModelException
+	 */
+	private static IPackageFragmentRoot findJreContainer(IJavaProject project) throws JavaModelException {
+		IPackageFragmentRoot jreContainer = null;
+
+		IPackageFragmentRoot[] allPackageFragmentRoots = project.getAllPackageFragmentRoots();
+		for (IPackageFragmentRoot packageFragmentRoot : allPackageFragmentRoots) {
+			if (packageFragmentRoot.getRawClasspathEntry().getPath().segment(0).contains("JRE_CONTAINER")) { //$NON-NLS-1$
+				jreContainer = packageFragmentRoot;
+				break;
+			}
+		}
+		return jreContainer;
 	}
 
 	/**