From 2dd8312cf2dec7be493578033d4686f980dd046b Mon Sep 17 00:00:00 2001 From: Aleksey Dobrynin Date: Thu, 6 Mar 2025 09:39:31 +0100 Subject: [PATCH] [java, jigsaw, index] prioritize newest Java descriptor in multi-release JARs (IDEA-365082) (cherry picked from commit 10d03d5095264cf4e708b6154b4f4a90ea683155) IJ-CR-156911 GitOrigin-RevId: 9af80db882a95d952565b74db2aa6e82e5dc6716 --- .../java/stubs/index/JavaModuleNameIndex.java | 95 ++++++++++++------ .../codeInsight/jigsaw/multi-release.jar | Bin 0 -> 3782 bytes .../daemon/ModuleHighlightingTest.kt | 62 +++++++++++- 3 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/jigsaw/multi-release.jar diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/java/stubs/index/JavaModuleNameIndex.java b/java/java-indexing-impl/src/com/intellij/psi/impl/java/stubs/index/JavaModuleNameIndex.java index f58f110d1a22..7226c18b7fb6 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/java/stubs/index/JavaModuleNameIndex.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/java/stubs/index/JavaModuleNameIndex.java @@ -1,22 +1,24 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.psi.impl.java.stubs.index; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.pom.java.JavaFeature; import com.intellij.psi.PsiJavaModule; import com.intellij.psi.impl.search.JavaSourceFilterScope; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StringStubIndexExtension; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.stubs.StubIndexKey; -import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.*; public final class JavaModuleNameIndex extends StringStubIndexExtension { + private static final int MIN_JAVA_VERSION = JavaFeature.MODULES.getMinimumLevel().feature(); private static final JavaModuleNameIndex ourInstance = new JavaModuleNameIndex(); public static JavaModuleNameIndex getInstance() { @@ -45,29 +47,44 @@ public final class JavaModuleNameIndex extends StringStubIndexExtension getModules(@NotNull String name, @NotNull Project project, @NotNull GlobalSearchScope scope) { Collection modules = StubIndex.getElements(getKey(), name, project, new JavaSourceFilterScope(scope), PsiJavaModule.class); if (modules.size() > 1) { - modules = filterVersions(project, modules); + modules = filterHighestVersions(project, modules); } return modules; } - private static Collection filterVersions(Project project, Collection modules) { - Set filter = new HashSet<>(); - + /** + * Filters the given collection of Java modules to exclude redundant versions of modules, + * preserving only the highest versions available in the project scope. + * + * @param project the project in which the module filtering is performed + * @param modules a collection of Java modules to be filtered + * @return a collection of Java modules with only the highest versions retained + */ + @NotNull + private static Collection filterHighestVersions(@NotNull Project project, @NotNull Collection modules) { ProjectFileIndex index = ProjectFileIndex.getInstance(project); - for (PsiJavaModule module : modules) { - VirtualFile root = index.getClassRootForFile(module.getContainingFile().getVirtualFile()); - if (root != null) { - List files = descriptorFiles(root); - VirtualFile main = ContainerUtil.getFirstItem(files); - if (main != null && !(root.equals(main.getParent()) || version(main.getParent()) >= 9)) { - filter.add(main); - } - for (int i = 1; i < files.size(); i++) { - filter.add(files.get(i)); + + Set roots = new HashSet<>(); + for (PsiJavaModule javaModule : modules) { + VirtualFile file = index.getClassRootForFile(javaModule.getContainingFile().getVirtualFile()); + ContainerUtil.addIfNotNull(roots, file); + } + + Set filter = new HashSet<>(); + for (VirtualFile root : roots) { + Collection descriptors = getSortedFileDescriptors(root); + boolean found = false; + // find the highest correct module. + for (VirtualFile descriptor : descriptors) { + if (!found && isCorrectModulePath(root, descriptor)) { + found = true; + } else { + filter.add(descriptor); } } } + // remove the same modules but with a smaller version. if (!filter.isEmpty()) { modules = ContainerUtil.filter(modules, m -> !filter.contains(m.getContainingFile().getVirtualFile())); } @@ -75,26 +92,50 @@ public final class JavaModuleNameIndex extends StringStubIndexExtension= MIN_JAVA_VERSION; + } + @Override public boolean traceKeyHashToVirtualFileMapping() { return true; } - private static List descriptorFiles(VirtualFile root) { - List results = new SmartList<>(); - - ContainerUtil.addIfNotNull(results, root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)); + /** + * Collects module descriptor files (e.g., `module-info.class`) from the root and "META-INF/versions", + * sorted by Java version (highest to lowest). + * + * @param root the root virtual file + * @return a sorted collection of module descriptor files + */ + @NotNull + private static Collection getSortedFileDescriptors(@NotNull VirtualFile root) { + NavigableMap results = new TreeMap<>((i1,i2) -> Integer.compare(i2, i1)); + VirtualFile rootModuleInfo = root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE); + if (rootModuleInfo != null) { + results.put(MIN_JAVA_VERSION, rootModuleInfo); + } VirtualFile versionsDir = root.findFileByRelativePath("META-INF/versions"); if (versionsDir != null) { VirtualFile[] versions = versionsDir.getChildren(); - Arrays.sort(versions, JavaModuleNameIndex::compareVersions); for (VirtualFile version : versions) { - ContainerUtil.addIfNotNull(results, version.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE)); + VirtualFile moduleInfo = version.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE); + if (moduleInfo != null) { + results.put(version(version), moduleInfo); + } } } - return results; + return results.values(); } private static int version(VirtualFile dir) { @@ -105,12 +146,4 @@ public final class JavaModuleNameIndex extends StringStubIndexExtensionQBC9*9L4WDrVy9sNArT!TaOeBDArI2o9w+z+JNxE)9@t>9*0WckL(zyLNy^@KH7 zgMk3+0lVcVST|+NKF56I(4o4E%UwQAJj-UYuOry+fe^n*srHhiQF~-F4T7)FihTUc z>27t?Y|F#9wbUOztWghm=c&vow4+f^On6`6Y5RMtN@j?I9EsgOEI^0m7p3dtR>1>R zky??MTac5AN2L&&%CyX);t~?nmt2C_vj zIVZ8WxYTcV-XRBpIY*DFJ=2tO@tht|AU~mlt@Bh9lS{oByJgCo7Y2cK+M+_N@(0`% zzwt$tExkGS;+w2>+ds1&V42GyTA}!dZ50Q{+_`_IDoy?wc4qQiFK?$0ON(RsIPd*F zuI18uLHkVD3j3Y^Pfod!QLJ(FiqgUPBH6AP$F0IQKeh`@)12HhH7{m~QMBh`-TJP& zpKJMB>i#^sudWy+;y!uR%5DEntW=g3WNw*rN35{8?3j7lp~3}LHjV!(S1Kg&%z3f! z+>Hu}3bVVFr+3~ehbAm&I4u0akZ}e_I6%TJ1QdGc;lRhIe{sQ-lE$Xr3y>W}jhvi431fw>dyZUE?_ms#}D#=TQ z#aEsQyOeSI{-X2S>s!|Um|w5#^d(rvXI0v*U&_n*a$UX%6ct4uIeSww`%KGYhtdMZ zxzJ#EICF}ik4|I~#%6sR}JI&93U?GQY zmP(jKH*M~noawhaF=wezpaYtyLrOEHD%2$-9|DK_K%xGb8;>x*oS6fbf(P3R7 z)OIdkWdF-2M>lyD&6a$6bCX}>p?MefgqkOsRL^kl_wQyXb$YwnMOW2Pb|HuC8>9Y2Qi<=iU zD?VOfTu{5^aq?@f|BTQw7g(8tf^UwbY5ZDX@ZAMs968!tAA63*5n#SOzI+D*L|V_g zUeppaP2sHl7p5R&_;KC;8Ig{^ve!AO!ojHT@6|WP5 zTAh7HN9*=PXEzDI(BZ9=(N??iUv1-5bx#ey`}+Kwnh3cY z2Q&}9?OZ2+rmZ`{D88zIm*SIm|dXYiDJcS7pTGm8jZEGLxd1!b%$*9N?b-mDnHVV zS4467AAH8cat}Ga&_J;!oDD|+A!jvsrHIc{xT-{0n1P~xCkeKpR*=Nmj9Qx@+x&wa bhd;3=I;<&!73g{f20org.example.first.First; + import org.example.second.Second; + import org.example.third.Third; + + public class Main { + }""".trimIndent()) + } + + IdeaTestUtil.withLevel(module, LanguageLevel.JDK_11) { + highlight("Main.java", """ + import org.example.first.First; + import org.example.second.Second; + import org.example.third.Third; + + public class Main { + }""".trimIndent()) + } + + IdeaTestUtil.withLevel(module, LanguageLevel.JDK_17) { + highlight("Main.java", """ + import org.example.first.First; + import org.example.second.Second; + import org.example.third.Third; + + public class Main { + }""".trimIndent()) + } + } + // private fun highlight(text: String) = highlight("module-info.java", text) @@ -1029,7 +1083,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { private fun withInternalJdk(moduleDescriptor: ModuleDescriptor, level: LanguageLevel, block: () -> Unit) { val name = "INTERNAL_JDK_TEST" - val module = getInstance(project).findModuleByName(moduleDescriptor.moduleName)!! + val module = ModuleManager.getInstance(project).findModuleByName(moduleDescriptor.moduleName)!! try { WriteAction.runAndWait(ThrowableRunnable { @@ -1059,13 +1113,13 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { val module = ModuleManager.getInstance(project).findModuleByName(moduleName) ?: return null val dummyRoot = VirtualFileManager.getInstance().findFileByUrl("temp:///") ?: return null dummyRoot.refresh(false, false) - val srcRoot = dummyRoot.createChildDirectory(this, "${srcPathPrefix}-${this.sourceRootName}"); + val srcRoot = dummyRoot.createChildDirectory(this, "${srcPathPrefix}-${this.sourceRootName}") val tempFs: TempFileSystem = srcRoot.getFileSystem() as TempFileSystem for (child in srcRoot.getChildren()) { if (!tempFs.exists(child)) { tempFs.createChildFile(this, srcRoot, child.getName()) } - child.delete(this); + child.delete(this) } ModuleRootModificationUtil.updateModel(module) { model -> model.addContentEntry(srcRoot).addSourceFolder(srcRoot, JavaSourceRootType.SOURCE)