Bug 565462 - SourceLookup + Java 11 + Find Duplicates broken

Don't use equals() to compare two class files. The equals() implemented
in AbstractClassFile considers attributes of parent container elements
that are irrelevant for debugger.

Change-Id: Ife6c4f5c18ed1d3ad3567533d5584c0af2ed5767
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
Signed-off-by: Simeon Andreev <simeon.danailov.andreev@gmail.com>
diff --git a/org.eclipse.jdt.debug.tests/testresources/bug565462/NonModuleJREProject/src/p2/Main.java b/org.eclipse.jdt.debug.tests/testresources/bug565462/NonModuleJREProject/src/p2/Main.java
new file mode 100644
index 0000000..7294643
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/bug565462/NonModuleJREProject/src/p2/Main.java
@@ -0,0 +1,12 @@
+package p2;
+
+import java.lang.reflect.Method;
+
+public class Main {
+	
+	public static void main(String[] args) throws NoSuchMethodException, SecurityException {
+		Method m = Main.class.getDeclaredMethod("Name", String[].class);
+		System.out.println(m);
+	}
+
+}
\ No newline at end of file
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/sourcelookup/Bug565462Tests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/sourcelookup/Bug565462Tests.java
new file mode 100644
index 0000000..e897c7d
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/sourcelookup/Bug565462Tests.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ *  Copyright (c) 2020 Simeon Andreev 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:
+ *     Simeon Andreev - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.debug.tests.sourcelookup;
+
+import java.util.Arrays;
+
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.debug.core.ILaunchConfiguration;
+import org.eclipse.jdt.core.IAccessRule;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.launching.JavaSourceLookupDirector;
+
+/**
+ * Tests for bug 565462.
+ */
+public class Bug565462Tests extends AbstractDebugTest {
+
+	private static final String MODULE_JRE_PROJECT_NAME = "ModuleJREProject";
+	private static final String NON_MODULE_JRE_PROJECT_NAME = "NonModuleJREProject";
+
+	public Bug565462Tests(String name) {
+		super(name);
+	}
+
+	/**
+	 * Test for bug 565462.
+	 *
+	 * Tests searching for a class with 2 Java projects in the workspace, one with {@code module=true} attribute for the JRE container, one without
+	 * that attribute.
+	 */
+	public void testFindDuplicatesBug565462() throws Exception {
+		IJavaProject moduleProject = createJavaProject(MODULE_JRE_PROJECT_NAME);
+		boolean attributeValue = true;
+		addModuleAttribute(moduleProject, attributeValue);
+		moduleProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
+		IJavaProject nonModuleProject = createJavaProject(NON_MODULE_JRE_PROJECT_NAME);
+		removeModuleAttribute(nonModuleProject);
+		nonModuleProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
+
+		waitForBuild();
+		assertTrue("Expected Java project to have module=true attribute: " + moduleProject, isModularProject(moduleProject));
+		assertFalse("Expected Java project to not have module attribute: " + nonModuleProject, isModularProject(nonModuleProject));
+
+		moduleProject.getProject().close(new NullProgressMonitor());
+		nonModuleProject.getProject().close(new NullProgressMonitor());
+		waitForBuild();
+		moduleProject.getProject().open(new NullProgressMonitor());
+		nonModuleProject.getProject().open(new NullProgressMonitor());
+		moduleProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
+		nonModuleProject.getProject().build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
+		waitForBuild();
+		assertTrue("Expected Java project to have module=true attribute: " + moduleProject, isModularProject(moduleProject));
+		assertFalse("Expected Java project to not have module attribute: " + nonModuleProject, isModularProject(nonModuleProject));
+
+		JavaSourceLookupDirector director = new JavaSourceLookupDirector();
+		ILaunchConfiguration configuration = createLaunchConfiguration(nonModuleProject, "Main");
+		director.initializeDefaults(configuration);
+		director.setFindDuplicates(true);
+
+		String className = "java/lang/Class.java";
+		Object[] foundElements = director.findSourceElements(className);
+		assertEquals("Expected only 1 match for class " + className + ", but found: " + Arrays.toString(foundElements), 1, foundElements.length);
+	}
+
+	private static void removeModuleAttribute(IJavaProject javaProject) throws JavaModelException {
+		boolean isModularProject = isModularProject(javaProject);
+		if (isModularProject) {
+			IClasspathEntry[] classpath = javaProject.getRawClasspath();
+			for (int i = 0; i < classpath.length; ++i) {
+				IClasspathEntry classpathEntry = classpath[i];
+				if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+					IPath containerPath = classpathEntry.getPath();
+					classpathEntry = JavaCore.newContainerEntry(containerPath);
+					classpath[i] = classpathEntry;
+				}
+			}
+			javaProject.setRawClasspath(classpath, new NullProgressMonitor());
+		}
+	}
+
+	private static void addModuleAttribute(IJavaProject javaProject, boolean attributeValue) throws JavaModelException {
+		boolean isModularProject = isModularProject(javaProject);
+		if (!isModularProject) {
+			IClasspathEntry[] classpath = javaProject.getRawClasspath();
+			for (int i = 0; i < classpath.length; ++i) {
+				IClasspathEntry classpathEntry = classpath[i];
+				if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+					IPath containerPath = classpathEntry.getPath();
+					IClasspathAttribute[] attributes = classpathEntry.getExtraAttributes();
+					IClasspathAttribute[] newAttributes = Arrays.copyOf(attributes, attributes.length + 1);
+					IClasspathAttribute moduleAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.MODULE, String.valueOf(attributeValue));
+					newAttributes[attributes.length] = moduleAttribute;
+					boolean isExported = false;
+					classpathEntry = JavaCore.newContainerEntry(containerPath, new IAccessRule[0], newAttributes, isExported);
+					classpath[i] = classpathEntry;
+				}
+			}
+			javaProject.setRawClasspath(classpath, new NullProgressMonitor());
+		}
+	}
+
+	private static boolean isModularProject(IJavaProject javaProject) throws JavaModelException {
+		IClasspathEntry[] classpath = javaProject.getRawClasspath();
+		return isModularClasspath(classpath);
+	}
+
+	private static boolean isModularClasspath(IClasspathEntry[] classpath) {
+		for (int i = 0; i < classpath.length; ++i) {
+			IClasspathEntry classpathEntry = classpath[i];
+			if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
+				boolean isModule = isModularEntry(classpathEntry);
+				return isModule;
+			}
+		}
+		return false;
+	}
+
+	private static boolean isModularEntry(IClasspathEntry classpathEntry) {
+		IClasspathAttribute[] attributes = classpathEntry.getExtraAttributes();
+		for (IClasspathAttribute attribute : attributes) {
+			String attributeName = attribute.getName();
+			if (IClasspathAttribute.MODULE.equals(attributeName)) {
+				String attributeValue = attribute.getValue();
+				boolean isModule = Boolean.parseBoolean(attributeValue);
+				return isModule;
+			}
+		}
+		return false;
+	}
+
+	private IJavaProject createJavaProject(String projectName) throws Exception {
+		IPath testrpath = new Path("testresources").append("bug565462");
+		IJavaProject javaProject = createJavaProjectClone(projectName, testrpath.append(projectName).toString(), "JavaSE-11", true);
+		javaProject.setOption(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_11);
+		javaProject.setOption(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_11);
+		javaProject.setOption(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_11);
+		javaProject.setOption(CompilerOptions.OPTION_Release, CompilerOptions.ENABLED);
+		return javaProject;
+	}
+}
diff --git a/org.eclipse.jdt.launching/META-INF/MANIFEST.MF b/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
index 1fee4af..692d8fe 100644
--- a/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.launching/META-INF/MANIFEST.MF
@@ -15,9 +15,9 @@
  org.eclipse.jdt.launching.sourcelookup.advanced,
  org.eclipse.jdt.launching.sourcelookup.containers
 Require-Bundle: org.eclipse.core.resources;bundle-version="[3.5.0,4.0.0)",
- org.eclipse.jdt.core;bundle-version="[3.22.0,4.0.0)",
- org.eclipse.debug.core;bundle-version="[3.14.0,4.0.0)",
- org.eclipse.jdt.debug;bundle-version="[3.11.0,4.0.0)",
+ org.eclipse.jdt.core;bundle-version="[3.24.0,4.0.0)",
+ org.eclipse.debug.core;bundle-version="[3.17.0,4.0.0)",
+ org.eclipse.jdt.debug;bundle-version="[3.17.0,4.0.0)",
  org.eclipse.core.variables;bundle-version="[3.2.0,4.0.0)",
  org.eclipse.core.runtime;bundle-version="[3.11.0,4.0.0)",
  org.eclipse.osgi;bundle-version="[3.8.0,4.0.0)",
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java
index 6b83d40..64c403a 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/JavaSourceLookupDirector.java
@@ -14,6 +14,7 @@
 package org.eclipse.jdt.internal.launching;
 
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector;
@@ -21,6 +22,7 @@
 import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant;
 import org.eclipse.debug.core.sourcelookup.containers.ProjectSourceContainer;
 import org.eclipse.debug.core.sourcelookup.containers.WorkspaceSourceContainer;
+import org.eclipse.jdt.internal.core.AbstractClassFile;
 import org.eclipse.jdt.launching.sourcelookup.containers.JavaSourceLookupParticipant;
 
 /**
@@ -40,18 +42,25 @@
 		fFilteredTypes.add("org.eclipse.debug.ui.containerType.workingSet"); //$NON-NLS-1$
 	}
 
-	/* (non-Javadoc)
-	 * @see org.eclipse.debug.internal.core.sourcelookup.ISourceLookupDirector#initializeParticipants()
-	 */
 	@Override
 	public void initializeParticipants() {
 		addParticipants(new ISourceLookupParticipant[] {new JavaSourceLookupParticipant()});
 	}
-	/* (non-Javadoc)
-	 * @see org.eclipse.debug.internal.core.sourcelookup.ISourceLookupDirector#supportsSourceContainerType(org.eclipse.debug.internal.core.sourcelookup.ISourceContainerType)
-	 */
+
 	@Override
 	public boolean supportsSourceContainerType(ISourceContainerType type) {
 		return !fFilteredTypes.contains(type.getId());
 	}
+
+	@Override
+	public boolean equalSourceElements(Object o1, Object o2) {
+		if (o1 instanceof AbstractClassFile && o2 instanceof AbstractClassFile) {
+			AbstractClassFile c1 = (AbstractClassFile) o1;
+			AbstractClassFile c2 = (AbstractClassFile) o2;
+			String pathIdentifier1 = c1.getPathIdentifier();
+			String pathIdentifier2 = c2.getPathIdentifier();
+			return Objects.equals(pathIdentifier1, pathIdentifier2);
+		}
+		return super.equalSourceElements(o1, o2);
+	}
 }
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/sourcelookup/containers/PackageFragmentRootSourceContainer.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/sourcelookup/containers/PackageFragmentRootSourceContainer.java
index fc67a9b..ecd0575 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/sourcelookup/containers/PackageFragmentRootSourceContainer.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/launching/sourcelookup/containers/PackageFragmentRootSourceContainer.java
@@ -144,4 +144,20 @@
 	public IPath getPath() {
 		return getPackageFragmentRoot().getPath();
 	}
+
+	@Override
+	public String toString() {
+		StringBuilder builder = new StringBuilder();
+		builder.append("["); //$NON-NLS-1$
+		if (fRoot != null) {
+			builder.append(fRoot.getElementName());
+			builder.append(", parent="); //$NON-NLS-1$
+			builder.append(fRoot.getParent().getElementName());
+			builder.append(", path="); //$NON-NLS-1$
+			builder.append(getPath());
+		}
+		builder.append("]"); //$NON-NLS-1$
+		return builder.toString();
+	}
+
 }