Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,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);
}
Expand Down
35 changes: 35 additions & 0 deletions org.eclipse.jdt.core/model/org/eclipse/jdt/core/IJavaProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,24 @@ IPackageFragmentRoot findPackageFragmentRoot(IPath path)
*/
IModuleDescription getModuleDescription() throws JavaModelException;

/**
* Returns the {@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.
*
* @param release
* specify the upper bound for the target multi-release, source folders that specify a release
* attribute are searched in descending order, starting with the value given by this parameter
* @return the {@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.44
*/
IModuleDescription getModuleDescription(int release) throws JavaModelException;

/**
* Returns the <code>IModuleDescription</code> owned by this project or
* null if the Java project doesn't own a valid Java module descriptor.
Expand All @@ -640,6 +658,23 @@ IPackageFragmentRoot findPackageFragmentRoot(IPath path)
*/
IModuleDescription getOwnModuleDescription() throws JavaModelException;

/**
* Returns the multi-release specific <code>IModuleDescription</code> owned by this project or <code>null</code> if
* the Java project doesn't own a valid 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.
*
* @param release
* specify the upper bound for the target multi-release, source folders that specify a release
* attribute are searched in descending order, starting with the value given by this parameter
* @return the {@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.44
*/
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,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;
Expand Down Expand Up @@ -3753,18 +3753,23 @@ 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<String> patchedModules = getPatchedModules(entry);
if (patchedModules.size() == 1) { // > 1 is malformed, 0 means not affecting this project
String mainModule = patchedModules.get(0);
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;
Expand All @@ -3783,6 +3788,36 @@ 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();
}
Expand Down Expand Up @@ -3824,11 +3859,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())));
Expand Down Expand Up @@ -3864,4 +3904,8 @@ public Manifest getManifest() {
public Set<String> determineModulesOfProjectsWithNonEmptyClasspath() throws JavaModelException {
return ModuleUpdater.determineModulesOfProjectsWithNonEmptyClasspath(this, getExpandedClasspath());
}

private static final record ReleaseClasspathEntry(IClasspathEntry entry, int release) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,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;
Expand Down Expand Up @@ -938,7 +938,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<IPackageFragmentRoot,IModuleDescription> cache, Function<IPackageFragmentRoot,IClasspathEntry> rootToEntry) {
static IModuleDescription getModuleDescription(JavaProject project, IPackageFragmentRoot root, Map<IPackageFragmentRoot,IModuleDescription> cache, Function<IPackageFragmentRoot,IClasspathEntry> rootToEntry, int release) {
IModuleDescription module = cache.get(root);
if (module != null)
return module != NO_MODULE ? module : null;
Expand All @@ -954,7 +954,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;
Expand All @@ -979,7 +979,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);
}
Expand Down Expand Up @@ -1103,9 +1103,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 {
Expand Down Expand Up @@ -1387,9 +1388,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();
Expand All @@ -1412,7 +1413,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading