diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/MultiReleaseTests.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/MultiReleaseTests.java index bb9b96e1dd9..ade860f5f88 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/MultiReleaseTests.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/MultiReleaseTests.java @@ -35,6 +35,10 @@ public class MultiReleaseTests extends BuilderTests { private static final String JAVA9_SRC_FOLDER = "src9"; private static final String DEFAULT_SRC_FOLDER = "src"; +// static { +// TESTS_NAMES = new String[] { "testMultiReleaseModuleInfoPerRelease" }; +// } + public MultiReleaseTests(String name) { super(name); } @@ -319,6 +323,92 @@ public String print() { expectingNoProblems(); } + /** + * Test multi-release compilation with different module-info.java per release. + * This verifies that each source folder with a different release uses its own + * module-info.java for compilation, not a shared module from the project. + * See issue https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4268 + */ + public void testMultiReleaseModuleInfoPerRelease() throws JavaModelException, IOException { + // Create modular project with Java 11 as base + IPath projectPath = createMRProject(CompilerOptions.VERSION_11); + IPath defaultSrc = env.getPackageFragmentRootPath(projectPath, DEFAULT_SRC_FOLDER); + + // Base module-info requires no extra modules + env.addClass(defaultSrc, "", "module-info", + """ + module MRmodular { + } + """ + ); + + // Base Test.java - should have errors for both java.desktop and java.xml types + IPath classDefault = env.addClass(defaultSrc, "p", "Test", + """ + package p; + public class Test { + java.awt.Window w11; + org.w3c.dom.Element element11; + } + """ + ); + + // Java 17 source with module-info requiring java.desktop + IClasspathAttribute[] attributes17 = new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "17") }; + IPath src17 = env.addPackageFragmentRoot(projectPath, "src17", attributes17); + env.addClass(src17, "", "module-info", + """ + module MRmodular { + requires java.desktop; + } + """ + ); + env.addClass(src17, "p", "Test", + """ + package p; + public class Test { + java.awt.Window w17; + org.w3c.dom.Element element17; + } + """ + ); + + // Java 21 source with module-info requiring java.xml + IClasspathAttribute[] attributes21 = new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "21") }; + IPath src21 = env.addPackageFragmentRoot(projectPath, "src21", attributes21); + env.addClass(src21, "", "module-info", + """ + module MRmodular { + requires java.xml; + } + """ + ); + IPath class21 = env.addClass(src21, "p", "Test", + """ + package p; + public class Test { + java.awt.Window w21; + org.w3c.dom.Element element21; + } + """ + ); + + fullBuild(); + //As our default module descriptor does not import anything both should give an error + expectingSpecificProblemsFor(defaultSrc, new Problem[] { // + new Problem("", "The type java.awt.Window is not accessible", classDefault, 32, 47, 40, IMarker.SEVERITY_ERROR), + new Problem("", "The type org.w3c.dom.Element is not accessible", classDefault, 54, 73, 40, IMarker.SEVERITY_ERROR) + }); + //java.desktop includes java.xml so no errors to expect here + expectingNoProblemsFor(src17); + //we only import java.xml so desktop should give an error! + expectingSpecificProblemsFor(src21, new Problem[] { // + new Problem("", "The type java.awt.Window is not accessible", class21, 32, 47, 40, IMarker.SEVERITY_ERROR), + }); + } + private IPath whenSetupMRRpoject() throws JavaModelException { return whenSetupMRRpoject(CompilerOptions.VERSION_1_8); } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java index 102de7b4839..b5a24e8a043 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AllJavaModelTests.java @@ -152,6 +152,8 @@ private static Class[] getAllTestClasses() { ReconcilerTests21.class, ReconcilerStatementsRecoveryTests.class, ReconcilerMultiReleaseTests.class, + ReconcilerModuleMultiReleaseTests.class, + SelectionMultiReleaseTests.class, // Copy and move operation tests CopyMoveElementsTests.class, diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerModuleMultiReleaseTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerModuleMultiReleaseTests.java new file mode 100644 index 00000000000..e96ae5cb0a0 --- /dev/null +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/ReconcilerModuleMultiReleaseTests.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2026 Christoph Läubrich 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 + *******************************************************************************/ +package org.eclipse.jdt.core.tests.model; + +import junit.framework.Test; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IClasspathAttribute; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IProblemRequestor; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; + +/** + * Tests that reconciling (problem detection in working copies) of a multi-release + * modular project resolves modules and types as seen from the release specific source + * folder the reconciled unit lives in, honoring a per-release {@code module-info.java}. + * + * See https://github.com/eclipse-jdt/eclipse.jdt.core/issues/4268 + */ +public class ReconcilerModuleMultiReleaseTests extends ModifyingResourceTests { + + static { +// TESTS_NAMES = new String[] { "testReconcileUsesReleaseModuleInfo21" }; + } + + public ReconcilerModuleMultiReleaseTests(String name) { + super(name); + } + + public static Test suite() { + return buildModelTestSuite(ReconcilerModuleMultiReleaseTests.class); + } + + @Override + public void setUpSuite() throws Exception { + super.setUpSuite(); + IJavaProject project = createJava9ProjectWithJREAttributes("ReconcilerModuleMR", + new String[] { "src", "src17", "src21" }, null, "21"); + IClasspathEntry[] classpath = project.getRawClasspath(); + for (int i = 0; i < classpath.length; i++) { + IClasspathEntry entry = classpath[i]; + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + if (entry.getPath().toString().endsWith("src17")) { + classpath[i] = JavaCore.newSourceEntry(entry.getPath(), null, null, null, + new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "17") }); + } else if (entry.getPath().toString().endsWith("src21")) { + classpath[i] = JavaCore.newSourceEntry(entry.getPath(), null, null, null, + new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "21") }); + } + } + } + project.setRawClasspath(classpath, new NullProgressMonitor()); + project.setOption(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED); + + createFolder("/ReconcilerModuleMR/src/p"); + createFolder("/ReconcilerModuleMR/src17/p"); + createFolder("/ReconcilerModuleMR/src21/p"); + + // base module requires nothing + createFile("/ReconcilerModuleMR/src/module-info.java", """ + module MRmodular { + } + """); + // release 17 requires java.desktop (which transitively reads java.xml) + createFile("/ReconcilerModuleMR/src17/module-info.java", """ + module MRmodular { + requires java.desktop; + } + """); + // release 21 requires java.xml + createFile("/ReconcilerModuleMR/src21/module-info.java", """ + module MRmodular { + requires java.xml; + } + """); + } + + @Override + public void tearDownSuite() throws Exception { + deleteProject("ReconcilerModuleMR"); + super.tearDownSuite(); + } + + private void assertReconcileProblems(String path, String source, String expectedProblems) throws Exception { + ProblemRequestor problemRequestor = new ProblemRequestor(); + WorkingCopyOwner owner = new WorkingCopyOwner() { + @Override + public IProblemRequestor getProblemRequestor(ICompilationUnit unit) { + return problemRequestor; + } + }; + ICompilationUnit wc = getWorkingCopy(path, source, owner, problemRequestor); + try { + problemRequestor.initialize(source.toCharArray()); + wc.reconcile(ICompilationUnit.NO_AST, true/*force problem detection*/, owner, null); + assertProblems("Unexpected problems for " + path, expectedProblems, problemRequestor); + } finally { + wc.discardWorkingCopy(); + } + } + + // java.xml is required in src21, so org.w3c.dom.Element must be accessible (no problem). + public void testReconcileUsesReleaseModuleInfo21() throws Exception { + assertReconcileProblems("/ReconcilerModuleMR/src21/p/Use.java", """ + package p; + public class Use { + org.w3c.dom.Element element; + } + """, "----------\n----------\n"); + } + + // java.desktop is required in src17 and reads java.xml transitively, so both are accessible. + public void testReconcileUsesReleaseModuleInfo17() throws Exception { + assertReconcileProblems("/ReconcilerModuleMR/src17/p/Use.java", """ + package p; + public class Use { + java.awt.Window window; + org.w3c.dom.Element element; + } + """, "----------\n----------\n"); + } + + // the base module requires nothing, so org.w3c.dom.Element is not accessible here. + public void testReconcileUsesBaseModuleInfo() throws Exception { + assertReconcileProblems("/ReconcilerModuleMR/src/p/Use.java", """ + package p; + public class Use { + org.w3c.dom.Element element; + } + """, + "----------\n" + + "1. ERROR in /ReconcilerModuleMR/src/p/Use.java (at line 3)\n" + + " org.w3c.dom.Element element;\n" + + " ^^^^^^^^^^^^^^^^^^^\n" + + "The type org.w3c.dom.Element is not accessible\n" + + "----------\n"); + } + + private ITypeBinding resolveFieldType(String path, String source) throws Exception { + ICompilationUnit wc = getWorkingCopy(path, source); + try { + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setResolveBindings(true); + parser.setSource(wc); + org.eclipse.jdt.core.dom.CompilationUnit ast = + (org.eclipse.jdt.core.dom.CompilationUnit) parser.createAST(null); + TypeDeclaration type = (TypeDeclaration) ast.types().get(0); + FieldDeclaration field = type.getFields()[0]; + return field.getType().resolveBinding(); + } finally { + wc.discardWorkingCopy(); + } + } + + // ASTParser.createAST (resolved DOM AST) must resolve org.w3c.dom.Element as seen from src21. + public void testCreateASTUsesReleaseModuleInfo21() throws Exception { + ITypeBinding binding = resolveFieldType("/ReconcilerModuleMR/src21/p/Use.java", """ + package p; + public class Use { + org.w3c.dom.Element element; + } + """); + assertNotNull("Type binding should be resolved", binding); + assertEquals("org.w3c.dom.Element", binding.getQualifiedName()); + } + + // java.desktop reads java.xml transitively, so the binding resolves in src17 as well. + public void testCreateASTUsesReleaseModuleInfo17() throws Exception { + ITypeBinding binding = resolveFieldType("/ReconcilerModuleMR/src17/p/Use.java", """ + package p; + public class Use { + org.w3c.dom.Element element; + } + """); + assertNotNull("Type binding should be resolved", binding); + assertEquals("org.w3c.dom.Element", binding.getQualifiedName()); + } +} diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SelectionMultiReleaseTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SelectionMultiReleaseTests.java new file mode 100644 index 00000000000..c450dd8534a --- /dev/null +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/SelectionMultiReleaseTests.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2026 Christoph Läubrich 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 + *******************************************************************************/ +package org.eclipse.jdt.core.tests.model; + +import junit.framework.Test; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IClasspathAttribute; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; + +/** + * Tests that code selection (Hover / Open Declaration) in a multi-release modular + * project resolves types as seen from the release specific source folder the selected + * unit lives in. + * + * See https://github.com/eclipse-jdt/eclipse.jdt.core/pull/4534#issuecomment-4743290623 + * where selecting a type that is only accessible through a release specific + * {@code module-info.java} produced multiple (duplicate) results because selection + * always resolved against the base {@code module-info.java}. + */ +public class SelectionMultiReleaseTests extends AbstractJavaModelTests { + + static { +// TESTS_NAMES = new String[] { "testSelectElementInRelease21" }; + } + + public SelectionMultiReleaseTests(String name) { + super(name); + } + + public static Test suite() { + return buildModelTestSuite(SelectionMultiReleaseTests.class); + } + + @Override + public void setUpSuite() throws Exception { + super.setUpSuite(); + IJavaProject project = createJava9ProjectWithJREAttributes("SelectionMR", + new String[] { "src", "src17", "src21" }, null, "21"); + IClasspathEntry[] classpath = project.getRawClasspath(); + for (int i = 0; i < classpath.length; i++) { + IClasspathEntry entry = classpath[i]; + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + if (entry.getPath().toString().endsWith("src17")) { + classpath[i] = JavaCore.newSourceEntry(entry.getPath(), null, null, null, + new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "17") }); + } else if (entry.getPath().toString().endsWith("src21")) { + classpath[i] = JavaCore.newSourceEntry(entry.getPath(), null, null, null, + new IClasspathAttribute[] { + JavaCore.newClasspathAttribute(IClasspathAttribute.RELEASE, "21") }); + } + } + } + project.setRawClasspath(classpath, new NullProgressMonitor()); + project.setOption(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED); + + createFolder("/SelectionMR/src/p"); + createFolder("/SelectionMR/src17/p"); + createFolder("/SelectionMR/src21/p"); + + // base module requires nothing + createFile("/SelectionMR/src/module-info.java", """ + module MRmodular { + } + """); + createFile("/SelectionMR/src/p/Test.java", """ + package p; + public class Test { + java.awt.Window w; + org.w3c.dom.Element element; + } + """); + + // release 17 requires java.desktop (which transitively reads java.xml) + createFile("/SelectionMR/src17/module-info.java", """ + module MRmodular { + requires java.desktop; + } + """); + createFile("/SelectionMR/src17/p/Test.java", """ + package p; + public class Test { + java.awt.Window w; + org.w3c.dom.Element element; + } + """); + + // release 21 requires java.xml + createFile("/SelectionMR/src21/module-info.java", """ + module MRmodular { + requires java.xml; + } + """); + createFile("/SelectionMR/src21/p/Test.java", """ + package p; + public class Test { + java.awt.Window w; + org.w3c.dom.Element element; + } + """); + } + + @Override + public void tearDownSuite() throws Exception { + deleteProject("SelectionMR"); + super.tearDownSuite(); + } + + private IType assertSingleType(String unitPath, String reference, String selection, String expectedFqn) + throws Exception { + ICompilationUnit unit = getCompilationUnit(unitPath); + String source = unit.getSource(); + int referenceStart = source.indexOf(reference); + assertTrue("reference '" + reference + "' not found in " + unitPath, referenceStart >= 0); + int start = source.indexOf(selection, referenceStart); + IJavaElement[] elements = unit.codeSelect(start, selection.length()); + StringBuilder details = new StringBuilder(); + for (IJavaElement element : elements) { + details.append('\n').append(element); + } + assertEquals("Expected exactly one selection result but got: " + details, 1, elements.length); + assertTrue("Selection result is not a type: " + elements[0], elements[0] instanceof IType); + IType type = (IType) elements[0]; + assertEquals("Unexpected resolved type", expectedFqn, type.getFullyQualifiedName()); + return type; + } + + // org.w3c.dom.Element is reachable in src21 via 'requires java.xml': selecting it must + // resolve to exactly that single type and not offer all the JDK types named 'Element'. + public void testSelectElementInRelease21() throws Exception { + assertSingleType("/SelectionMR/src21/p/Test.java", "org.w3c.dom.Element", "Element", + "org.w3c.dom.Element"); + } + + // java.awt.Window is reachable in src17 via 'requires java.desktop'. + public void testSelectWindowInRelease17() throws Exception { + assertSingleType("/SelectionMR/src17/p/Test.java", "java.awt.Window", "Window", + "java.awt.Window"); + } + + // java.desktop reads java.xml transitively, so org.w3c.dom.Element is reachable in src17 too. + public void testSelectElementInRelease17() throws Exception { + assertSingleType("/SelectionMR/src17/p/Test.java", "org.w3c.dom.Element", "Element", + "org.w3c.dom.Element"); + } +} diff --git a/org.eclipse.jdt.core/META-INF/MANIFEST.MF b/org.eclipse.jdt.core/META-INF/MANIFEST.MF index 171a059f602..642bb31c198 100644 --- a/org.eclipse.jdt.core/META-INF/MANIFEST.MF +++ b/org.eclipse.jdt.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jdt.core; singleton:=true -Bundle-Version: 3.46.100.qualifier +Bundle-Version: 3.47.0.qualifier Bundle-Activator: org.eclipse.jdt.core.JavaCore Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java index ce5dcbf7898..74ff1b9fc77 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java @@ -716,7 +716,11 @@ public static void resolve( try { int amountOfWork = (compilationUnits.length + bindingKeys.length) * 2; // 1 for beginToCompile, 1 for resolve SubMonitor subMonitor = SubMonitor.convert(monitor, amountOfWork); - environment = new CancelableNameEnvironment(((JavaProject) javaProject), owner, subMonitor); + // resolve as seen from the source folder the units live in, honoring a release specific module-info.java + int release = compilationUnits.length > 0 + ? JavaProject.getRelease(compilationUnits[0]) + : JavaProject.NO_RELEASE; + environment = new CancelableNameEnvironment(((JavaProject) javaProject), owner, subMonitor, false, release); problemFactory = new CancelableProblemFactory(subMonitor); CompilerOptions compilerOptions = getCompilerOptions(options, (flags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); compilerOptions.ignoreMethodBodies = (flags & ICompilationUnit.IGNORE_METHOD_BODIES) != 0; @@ -810,7 +814,12 @@ public static CompilationUnitDeclaration resolve( classpaths.toArray(allEntries); environment = new NameEnvironmentWithProgress(allEntries, null, monitor); } else { - environment = new CancelableNameEnvironment((JavaProject) javaProject, owner, monitor); + // resolve as seen from the source folder the unit lives in, honoring a release specific module-info.java + char[] fileName = sourceUnit.getFileName(); + int release = fileName != null && fileName.length > 0 + ? ((JavaProject) javaProject).getRelease(IPath.fromPortableString(new String(fileName))) + : JavaProject.NO_RELEASE; + environment = new CancelableNameEnvironment((JavaProject) javaProject, owner, monitor, false, release); } problemFactory = new CancelableProblemFactory(monitor); CompilerOptions compilerOptions = getCompilerOptions(options, (flags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) != 0); diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ModuleBinding.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ModuleBinding.java index c5c637ee198..7675bdd1cd0 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ModuleBinding.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ModuleBinding.java @@ -19,6 +19,7 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.env.INameEnvironment; import org.eclipse.jdt.internal.compiler.util.Util; +import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.NameLookup; import org.eclipse.jdt.internal.core.NameLookup.Answer; import org.eclipse.jdt.internal.core.SearchableEnvironment; @@ -124,7 +125,7 @@ public IJavaElement getJavaElement() { if (!(nameEnvironment instanceof SearchableEnvironment)) return null; NameLookup nameLookup = ((SearchableEnvironment) nameEnvironment).nameLookup; if (nameLookup == null) return null; - Answer answer = nameLookup.findModule(this.getName().toCharArray()); + Answer answer = nameLookup.findModule(this.getName().toCharArray(), JavaProject.NO_RELEASE); if (answer == null) return null; return answer.module; } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IJavaProject.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IJavaProject.java index 7efc551c598..93ea3715a88 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IJavaProject.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/core/IJavaProject.java @@ -625,6 +625,32 @@ IPackageFragmentRoot findPackageFragmentRoot(IPath path) */ IModuleDescription getModuleDescription() throws JavaModelException; + /** + * Returns an {@link IModuleDescription} this project represents or null if the Java project doesn't represent any + * named module. A Java project is said to represent a module if any of its source package fragment roots (see + * {@link IPackageFragmentRoot#K_SOURCE}) contains a valid Java module descriptor, or if one of its classpath + * entries has a valid {@link IClasspathAttribute#PATCH_MODULE} attribute affecting the current project. In the + * latter case the corresponding module description of the location referenced by that classpath entry is returned. + * + *

Furthermore, if the project is multi-release aware, then the specified release + * selects the most specific suitable module description. For this purpose the {@link IClasspathAttribute#RELEASE} + * attribute of each source folder is inspected, if present. Source folders are then search from the requested release down + * to the lowest release and finally to a source folder with no {@link IClasspathAttribute#RELEASE} attribute. + * The first valid module description found in a source folder visited during this search is then returned.

+ * + *

A module description contributed via {@link IClasspathAttribute#PATCH_MODULE} is considered only if the + * regular search finds no module description

+ * + * @param release + * Specify the upper bound for version specific source folders to be searched. + * @return a {@link IModuleDescription} this project represents. + * @exception JavaModelException + * if this element does not exist or if an exception occurs while accessing its + * corresponding resource + * @since 3.47 + */ + IModuleDescription getModuleDescription(int release) throws JavaModelException; + /** * Returns the IModuleDescription owned by this project or * null if the Java project doesn't own a valid Java module descriptor. @@ -640,6 +666,28 @@ IPackageFragmentRoot findPackageFragmentRoot(IPath path) */ IModuleDescription getOwnModuleDescription() throws JavaModelException; + /** + * Returns an {@link IModuleDescription} owned by this project or null if + * the Java project doesn't own a suitable Java module descriptor. + * This method considers only module descriptions contained in any of the project's source package fragment roots + * (see {@link IPackageFragmentRoot#K_SOURCE}). In + * particular any {@link IClasspathAttribute#PATCH_MODULE} attribute is not considered. + *

Furthermore, if the project is multi-release aware, then the specified release + * selects the most specific suitable module description. For this purpose the {@link IClasspathAttribute#RELEASE} + * attribute of each source folder is inspected, if present. Source folders are then search from the requested release down + * to the lowest release and finally to a source folder with no {@link IClasspathAttribute#RELEASE} attribute. + * The first valid module description found in a source folder visited during this search is then returned.

+ * + * @param release + * Specify the upper bound for version specific source folders to be searched. + * @return a {@link IModuleDescription} this project owns. + * @exception JavaModelException + * if this element does not exist or if an exception occurs while accessing its + * corresponding resource + * @since 3.47 + */ + IModuleDescription getOwnModuleDescription(int release) throws JavaModelException; + /** * Returns the raw classpath for the project, as a list of classpath * entries. This corresponds to the exact set of entries which were assigned diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java index 17a149aa1a3..1ce5b2db1c3 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/BinaryType.java @@ -116,7 +116,8 @@ public void codeComplete( throw new IllegalArgumentException("Completion requestor cannot be null"); //$NON-NLS-1$ } JavaProject project = getJavaProject(); - SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, requestor.isTestCodeExcluded()); + int release = JavaProject.getRelease(this); + SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, requestor.isTestCodeExcluded(), release); CompletionEngine engine = new CompletionEngine(environment, requestor, project.getOptions(true), project, owner, monitor); String source = getClassFile().getSource(); diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java index 54f7167edd6..79ffdea645b 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/CompilationUnitProblemFinder.java @@ -236,23 +236,10 @@ private static boolean isTestSource(IJavaProject project, ICompilationUnit cu) { } private static int getRelease(IJavaProject project, ICompilationUnit cu) { - try { - IClasspathEntry[] rawClasspath = project.getRawClasspath(); - final IPath resourcePath = cu.getResource().getFullPath(); - for (IClasspathEntry e : rawClasspath) { - if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE && e.getPath().isPrefixOf(resourcePath)) { - String value = ClasspathEntry.getExtraAttribute(e, IClasspathAttribute.RELEASE); - if (value != null) - return Integer.parseInt(value); - } - } - } catch (JavaModelException | NumberFormatException e) { - Util.log(e, "Exception while determining the release value for compilation unit \"" + cu.getElementName() //$NON-NLS-1$ - + "\"."); //$NON-NLS-1$ - } - return JavaProject.NO_RELEASE; + return JavaProject.getRelease(cu); } + /* * Can return null if the process was aborted or canceled */ diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java index a0176f8de54..3aa26114d4e 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java @@ -1749,7 +1749,7 @@ public IModuleDescription findModule(String moduleName, WorkingCopyOwner owner) * Internal findModule with instantiated name lookup */ IModuleDescription findModule(String moduleName, NameLookup lookup) throws JavaModelException { - NameLookup.Answer answer = lookup.findModule(moduleName.toCharArray()); + NameLookup.Answer answer = lookup.findModule(moduleName.toCharArray(), NO_RELEASE); if (answer != null) return answer.module; return null; @@ -2842,7 +2842,14 @@ public SearchableEnvironment newSearchableNameEnvironment(ICompilationUnit[] wor * Returns a new search name environment for this project. This name environment first looks in the given working copies. */ public SearchableEnvironment newSearchableNameEnvironment(ICompilationUnit[] workingCopies, boolean excludeTestCode) throws JavaModelException { - return new SearchableEnvironment(this, workingCopies, excludeTestCode, NO_RELEASE); + return newSearchableNameEnvironment(workingCopies, excludeTestCode, NO_RELEASE); + } + /* + * Returns a new search name environment for this project that resolves types and modules as seen from a source + * folder targeting the given {@code release} (see {@link IClasspathAttribute#RELEASE}). + */ + public SearchableEnvironment newSearchableNameEnvironment(ICompilationUnit[] workingCopies, boolean excludeTestCode, int release) throws JavaModelException { + return new SearchableEnvironment(this, workingCopies, excludeTestCode, release); } /* @@ -2853,7 +2860,75 @@ public SearchableEnvironment newSearchableNameEnvironment(WorkingCopyOwner owner return newSearchableNameEnvironment(owner, false); } public SearchableEnvironment newSearchableNameEnvironment(WorkingCopyOwner owner, boolean excludeTestCode) throws JavaModelException { - return new SearchableEnvironment(this, owner, excludeTestCode, NO_RELEASE); + return newSearchableNameEnvironment(owner, excludeTestCode, NO_RELEASE); + } + /* + * Returns a new search name environment for this project that resolves types and modules as seen from a source + * folder targeting the given {@code release} (see {@link IClasspathAttribute#RELEASE}). This is needed for + * multi-release projects where a release specific source folder may declare its own {@code module-info.java}. + */ + public SearchableEnvironment newSearchableNameEnvironment(WorkingCopyOwner owner, boolean excludeTestCode, int release) throws JavaModelException { + return new SearchableEnvironment(this, owner, excludeTestCode, release); + } + + /* + * Returns the release a source folder represented by the given classpath entry targets by inspecting its + * {@link IClasspathAttribute#RELEASE} attribute, or {@link #NO_RELEASE} if the entry has no (valid) release + * attribute. + */ + public static int getRelease(IClasspathEntry entry) { + if (entry != null) { + String attribute = ClasspathEntry.getExtraAttribute(entry, IClasspathAttribute.RELEASE); + if (attribute != null) { + try { + return Integer.parseInt(attribute); + } catch (NumberFormatException e) { + // can't determine the release from the classpath so assume default release, + // this would already be reported at other places. + } + } + } + return NO_RELEASE; + } + + /* + * Returns the release the source folder containing the given element targets (see + * {@link IClasspathAttribute#RELEASE}), or {@link #NO_RELEASE} if the element is not contained in a release + * specific source folder (e.g. it lives in a library or in a folder without a release attribute). + */ + public static int getRelease(IJavaElement element) { + if (element == null) { + return NO_RELEASE; + } + IPackageFragmentRoot root = (IPackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); + if (root == null) { + return NO_RELEASE; + } + try { + return getRelease(root.getResolvedClasspathEntry()); + } catch (JavaModelException e) { + return NO_RELEASE; + } + } + + /* + * Returns the release the source folder of this project containing the given resource path targets (see + * {@link IClasspathAttribute#RELEASE}), or {@link #NO_RELEASE} if the path is not located in a release specific + * source folder. + */ + public int getRelease(IPath sourcePath) { + if (sourcePath != null) { + try { + for (IClasspathEntry entry : getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE && entry.getPath().isPrefixOf(sourcePath)) { + return getRelease(entry); + } + } + } catch (JavaModelException e) { + // can't determine the release, assume default release + } + } + return NO_RELEASE; } /* @@ -3759,10 +3834,15 @@ protected IStatus validateExistence(IResource underlyingResource) { @Override public IModuleDescription getModuleDescription() throws JavaModelException { - JavaProjectElementInfo info = (JavaProjectElementInfo) getElementInfo(); - IModuleDescription module = info.getModule(); - if (module != null) + return getModuleDescription(NO_RELEASE); + } + + @Override + public IModuleDescription getModuleDescription(int release) throws JavaModelException { + IModuleDescription module = getOwnModuleDescription(release); + if (module != null) { return module; + } for(IClasspathEntry entry : getRawClasspath()) { List patchedModules = getPatchedModules(entry); if (patchedModules.size() == 1) { // > 1 is malformed, 0 means not affecting this project @@ -3770,7 +3850,7 @@ public IModuleDescription getModuleDescription() throws JavaModelException { switch (entry.getEntryKind()) { case IClasspathEntry.CPE_PROJECT: IJavaProject referencedProject = getJavaModel().getJavaProject(entry.getPath().toString()); - module = referencedProject.getModuleDescription(); + module = referencedProject.getModuleDescription(release); if (module != null && module.getElementName().equals(mainModule)) return module; break; @@ -3789,6 +3869,40 @@ public IModuleDescription getModuleDescription() throws JavaModelException { @Override public IModuleDescription getOwnModuleDescription() throws JavaModelException { + return getOwnModuleDescription(NO_RELEASE); + } + + @Override + public IModuleDescription getOwnModuleDescription(int release) throws JavaModelException { + if (release >= FIRST_MULTI_RELEASE) { + IModuleDescription releaseSpecificDescriptor = Arrays.stream(getRawClasspath()) + .map(e -> { + String attribute = ClasspathEntry.getExtraAttribute(e, IClasspathAttribute.RELEASE); + if (attribute != null) { + try { + return new ReleaseClasspathEntry(e, Integer.parseInt(attribute)); + } catch (NumberFormatException nfe) { + // can't use then + } + } + return null; + }) + .filter(Objects::nonNull).filter(entry -> entry.release() <= release) + .sorted(Comparator.comparingInt(ReleaseClasspathEntry::release).reversed()) + .map(entry -> { + for (IPackageFragmentRoot root : findPackageFragmentRoots(entry.entry())) { + IModuleDescription module = root.getModuleDescription(); + if (module != null) { + return module; + } + } + return null; + }) + .filter(Objects::nonNull).findFirst().orElse(null); + if (releaseSpecificDescriptor != null) { + return releaseSpecificDescriptor; + } + } JavaProjectElementInfo info = (JavaProjectElementInfo) getElementInfo(); return info.getModule(); } @@ -3830,11 +3944,16 @@ public IModuleDescription getAutomaticModuleDescription() throws JavaModelExcept } public void setModuleDescription(IModuleDescription module) throws JavaModelException { + IPackageFragmentRoot newRoot = (IPackageFragmentRoot) module.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); + IClasspathEntry classpathEntry = newRoot.getRawClasspathEntry(); + if (ClasspathEntry.getExtraAttribute(classpathEntry, IClasspathAttribute.RELEASE) != null) { + // Do not update the projects module descriptor with something from a release folder! + return; + } JavaProjectElementInfo info = (JavaProjectElementInfo) getElementInfo(); IModuleDescription current = info.getModule(); if (current != null) { IPackageFragmentRoot root = (IPackageFragmentRoot) current.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); - IPackageFragmentRoot newRoot = (IPackageFragmentRoot) module.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); if (!root.equals(newRoot)) throw new JavaModelException(new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, Messages.bind(Messages.classpath_duplicateEntryPath, TypeConstants.MODULE_INFO_FILE_NAME_STRING, getElementName()))); @@ -3870,4 +3989,8 @@ public Manifest getManifest() { public Set determineModulesOfProjectsWithNonEmptyClasspath() throws JavaModelException { return ModuleUpdater.determineModulesOfProjectsWithNonEmptyClasspath(this, getExpandedClasspath()); } + + private static final record ReleaseClasspathEntry(IClasspathEntry entry, int release) { + + } } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NameLookup.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NameLookup.java index 7870536f0d7..4b33cdd5b92 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NameLookup.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NameLookup.java @@ -854,7 +854,7 @@ public Answer findType( } } Answer answer = new Answer(type, accessRestriction, entry, - getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get)); + getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get, release)); if (!answer.ignoreIfBetter()) { if (answer.isBetter(suggestedAnswer)) return answer; @@ -914,19 +914,7 @@ else if (suggestedAnswer == null && considerSecondaryTypes) { } private int getRelease(IPackageFragmentRoot root) { - IClasspathEntry entry = this.rootToResolvedEntries.get(root); - if (entry != null) { - String extraAttributes = ClasspathEntry.getExtraAttribute(entry, IClasspathAttribute.RELEASE); - if (extraAttributes != null) { - try { - return Integer.parseInt(extraAttributes); - } catch (NumberFormatException e) { - // we can't determine the release from the classpath so assume default release, - // this would already be reported at other places. - } - } - } - return JavaProject.NO_RELEASE; + return JavaProject.getRelease(this.rootToResolvedEntries.get(root)); } public static IModule getModuleDescriptionInfo(IModuleDescription moduleDesc) { @@ -947,7 +935,7 @@ public static IModule getModuleDescriptionInfo(IModuleDescription moduleDesc) { } /** Internal utility, which is able to answer explicit and automatic modules. */ - static IModuleDescription getModuleDescription(JavaProject project, IPackageFragmentRoot root, Map cache, Function rootToEntry) { + static IModuleDescription getModuleDescription(JavaProject project, IPackageFragmentRoot root, Map cache, Function rootToEntry, int release) { IModuleDescription module = cache.get(root); if (module != null) return module != NO_MODULE ? module : null; @@ -963,7 +951,7 @@ static IModuleDescription getModuleDescription(JavaProject project, IPackageFrag } try { if (root.getKind() == IPackageFragmentRoot.K_SOURCE) - module = root.getJavaProject().getModuleDescription(); // from any root in this project + module = root.getJavaProject().getModuleDescription(release); // from any root in this project } catch (JavaModelException e) { cache.put(root, NO_MODULE); return null; @@ -988,7 +976,7 @@ static IModuleDescription getModuleDescription(JavaProject project, IPackageFrag } public IModule getModuleDescriptionInfo(PackageFragmentRoot root) { - IModuleDescription desc = getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get); + IModuleDescription desc = getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get, JavaProject.NO_RELEASE); if (desc != null) { return getModuleDescriptionInfo(desc); } @@ -1112,9 +1100,10 @@ public Answer findType(String name, boolean partialMatch, int acceptFlags, boole } return findType(className, packageName, partialMatch, acceptFlags, considerSecondaryTypes, waitForIndexes, checkRestrictions, monitor); } - public Answer findModule(char[] moduleName) { + + public Answer findModule(char[] moduleName, int release) { JavaElementRequestor requestor = new JavaElementRequestor(); - seekModule(moduleName, false, requestor); + seekModule(moduleName, false, requestor, release); IModuleDescription[] modules = requestor.getModules(); if (modules.length == 0) { try { @@ -1396,9 +1385,9 @@ public void seekTypes(String name, IPackageFragment pkg, boolean partialMatch, i } public void seekModuleReferences(String name, IJavaElementRequestor requestor, IJavaProject javaProject) { - seekModule(name.toCharArray(), true /* prefix */, requestor); + seekModule(name.toCharArray(), true /* prefix */, requestor, JavaProject.NO_RELEASE); } - public void seekModule(char[] name, boolean prefixMatch, IJavaElementRequestor requestor) { + public void seekModule(char[] name, boolean prefixMatch, IJavaElementRequestor requestor, int release) { long start = -1; if (VERBOSE) start = System.currentTimeMillis(); @@ -1421,7 +1410,7 @@ public void seekModule(char[] name, boolean prefixMatch, IJavaElementRequestor r continue; } } - module = getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get); + module = getModuleDescription(this.rootProject, root, this.rootToModule, this.rootToResolvedEntries::get, release); if (module != null && prefixMatcher.matches(name, module.getElementName().toCharArray(), false)) { requestor.acceptModule(module); } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NamedMember.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NamedMember.java index 86b47a1ef0d..08812e37ff2 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NamedMember.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/NamedMember.java @@ -266,7 +266,8 @@ public String[][] resolveType(String typeName) throws JavaModelException { */ public String[][] resolveType(String typeName, WorkingCopyOwner owner) throws JavaModelException { JavaProject project = getJavaProject(); - SearchableEnvironment environment = project.newSearchableNameEnvironment(owner); + int release = JavaProject.getRelease(this); + SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, false, release); class TypeResolveRequestor implements ISelectionRequestor { String[][] answers = null; diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java index 3129dacb26c..5421f952f88 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/Openable.java @@ -128,7 +128,8 @@ protected void codeComplete( throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS)); } JavaProject project = getJavaProject(); - SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, requestor.isTestCodeExcluded()); + int release = JavaProject.getRelease(this); + SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, requestor.isTestCodeExcluded(), release); // set unit to skip environment.unitToSkip = unitToSkip; @@ -154,8 +155,10 @@ protected IJavaElement[] codeSelect(org.eclipse.jdt.internal.compiler.env.ICompi } JavaProject project = getJavaProject(); - SearchableEnvironment environment = project.newSearchableNameEnvironment(owner); - + // resolve types and modules as seen from the source folder the unit lives in, so that a release specific + // module-info.java (multi-release project) is honoured (avoids duplicate selection results). + int release = JavaProject.getRelease(this); + SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, false, release); SelectionRequestor requestor= new SelectionRequestor(environment.nameLookup, this); IBuffer buffer = getBuffer(); if (buffer == null) { diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SearchableEnvironment.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SearchableEnvironment.java index 0c71b37ceef..487b8a3510a 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SearchableEnvironment.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SearchableEnvironment.java @@ -308,7 +308,7 @@ private NameEnvironmentAnswer createAnswer(Answer lookupAnswer, String packageNa * ISearchRequestor.acceptModule(char[][] moduleName) */ public void findModules(char[] prefix, ISearchRequestor requestor, IJavaProject javaProject) { - this.nameLookup.seekModule(prefix, true, new SearchableEnvironmentRequestor(requestor)); + this.nameLookup.seekModule(prefix, true, new SearchableEnvironmentRequestor(requestor), this.release); } @Override @@ -1137,7 +1137,7 @@ private IModuleDescription getModuleDescription(IPackageFragmentRoot[] roots) { this.rootToModule = new HashMap<>(); } for (IPackageFragmentRoot root : roots) { - IModuleDescription moduleDescription = NameLookup.getModuleDescription(this.project, root, this.rootToModule, this.nameLookup.rootToResolvedEntries::get); + IModuleDescription moduleDescription = NameLookup.getModuleDescription(this.project, root, this.rootToModule, this.nameLookup.rootToResolvedEntries::get, this.release); if (moduleDescription != null) return moduleDescription; } @@ -1149,7 +1149,7 @@ private IPackageFragmentRoot[] findModuleContext(char[] moduleName) { if (this.knownModuleLocations != null && moduleName != null && moduleName.length > 0) { moduleContext = this.knownModuleLocations.get(String.valueOf(moduleName)); if (moduleContext == null) { - Answer moduleAnswer = this.nameLookup.findModule(moduleName); + Answer moduleAnswer = this.nameLookup.findModule(moduleName, this.release); if (moduleAnswer != null) { IProject currentProject = moduleAnswer.module.getJavaProject().getProject(); IJavaElement current = moduleAnswer.module.getParent(); @@ -1223,7 +1223,7 @@ public void cleanup() { @Override public org.eclipse.jdt.internal.compiler.env.IModule getModule(char[] name) { - NameLookup.Answer answer = this.nameLookup.findModule(name); + NameLookup.Answer answer = this.nameLookup.findModule(name, this.release); IModule module = null; if (answer != null) { module = NameLookup.getModuleDescriptionInfo(answer.module); diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SelectionRequestor.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SelectionRequestor.java index 94da6a35e51..eaac735f1cc 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SelectionRequestor.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SelectionRequestor.java @@ -886,7 +886,7 @@ public IJavaElement[] getElements() { return this.elements; } protected IModuleDescription resolveModule(char[] moduleName) { - Answer answer = this.nameLookup.findModule(moduleName); + Answer answer = this.nameLookup.findModule(moduleName, JavaProject.NO_RELEASE); if (answer != null) { return answer.module; } diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java index 99cd8127d58..d96ea03fec2 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/SourceType.java @@ -124,7 +124,8 @@ public void codeComplete( } JavaProject project = getJavaProject(); - SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, requestor.isTestCodeExcluded()); + int release = JavaProject.getRelease(this); + SearchableEnvironment environment = project.newSearchableNameEnvironment(owner, requestor.isTestCodeExcluded(), release); CompletionEngine engine = new CompletionEngine(environment, requestor, project.getOptions(true), project, owner, monitor); String source = getCompilationUnit().getSource(); diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java index 84a7015323f..5fb504e8e6a 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/builder/NameEnvironment.java @@ -154,7 +154,7 @@ private void computeClasspathLocations( this.moduleUpdater.addReadUnnamedForNonEmptyClasspath(javaProject, classpathEntries); } } - IModuleDescription projectModule = javaProject.getModuleDescription(); + IModuleDescription projectModule = javaProject.getModuleDescription(releaseTarget); String patchedModuleName = ModuleEntryProcessor.pushPatchToFront(classpathEntries, javaProject); IModule patchedModule = null; @@ -402,6 +402,7 @@ private void computeClasspathLocations( try { AbstractModule sourceModule = (AbstractModule)projectModule; IModule info = (IModule) sourceModule.getElementInfo(); + // Add all source locations to the module path entry final ClasspathLocation[] sourceLocations2; if(sLocationsForTest.size() == 0) { sourceLocations2 = this.sourceLocations; diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java index 8c2d4d4d280..53fed18e89b 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/HierarchyBuilder.java @@ -94,7 +94,8 @@ public HierarchyBuilder(TypeHierarchy hierarchy) throws JavaModelException { unitsToLookInside = workingCopies; } if (project != null) { - SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(unitsToLookInside); + int release = JavaProject.getRelease(focusType); + SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(unitsToLookInside, false, release); this.nameLookup = searchableEnvironment.nameLookup; this.hierarchyResolver = new HierarchyResolver( diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java index 90bfc0ac407..5cc5cad7628 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/IndexBasedHierarchyBuilder.java @@ -214,7 +214,8 @@ public int compare(Object a, Object b) { } } - SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(unitsToLookInside); + int release = inProjectOfFocusType ? JavaProject.getRelease(focusType) : JavaProject.NO_RELEASE; + SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(unitsToLookInside, false, release); this.nameLookup = searchableEnvironment.nameLookup; Map options = project.getOptions(true); // disable task tags to speed up parsing diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/RegionBasedHierarchyBuilder.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/RegionBasedHierarchyBuilder.java index 00cec01afe1..659ad0468dc 100644 --- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/RegionBasedHierarchyBuilder.java +++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/hierarchy/RegionBasedHierarchyBuilder.java @@ -80,7 +80,11 @@ private void createTypeHierarchyBasedOnRegion(HashMap allOpenablesInRegion, IPro try { // resolve - SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(this.hierarchy.workingCopies); + IType focusType = this.hierarchy.focusType; + int release = focusType != null && project.equals(focusType.getJavaProject()) + ? JavaProject.getRelease(focusType) + : JavaProject.NO_RELEASE; + SearchableEnvironment searchableEnvironment = project.newSearchableNameEnvironment(this.hierarchy.workingCopies, false, release); this.nameLookup = searchableEnvironment.nameLookup; this.hierarchyResolver.resolve(openables, null, monitor); } catch (JavaModelException e) { diff --git a/org.eclipse.jdt.core/pom.xml b/org.eclipse.jdt.core/pom.xml index 05d45844bff..de774adf31e 100644 --- a/org.eclipse.jdt.core/pom.xml +++ b/org.eclipse.jdt.core/pom.xml @@ -17,7 +17,7 @@ 4.41.0-SNAPSHOT org.eclipse.jdt.core - 3.46.100-SNAPSHOT + 3.47.0-SNAPSHOT eclipse-plugin