diff --git a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java index 9ffe6bab2e3f..35a5e9e9848f 100644 --- a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java +++ b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiModuleUtil.java @@ -23,6 +23,7 @@ import com.intellij.psi.search.ProjectScope; import com.intellij.psi.search.searches.JavaModuleSearch; import com.intellij.psi.util.*; import com.intellij.util.ArrayUtil; +import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.graph.DFSTBuilder; @@ -259,7 +260,8 @@ public final class JavaPsiModuleUtil { return requireNonNullElse(ContainerUtil.find(cycles, set -> set.contains(module)), Collections.emptyList()); } - private static @Nullable VirtualFile getVirtualFile(@NotNull PsiJavaModule module) { + private static @Nullable VirtualFile getVirtualFile(@Nullable PsiJavaModule module) { + if (module == null) return null; if (module instanceof LightJavaModule light) { return light.getRootVirtualFile(); } @@ -385,23 +387,19 @@ public final class JavaPsiModuleUtil { * The resulting graph is used for tracing readability and checking package conflicts. */ private static @NotNull RequiresGraph buildRequiresGraph(@NotNull Project project) { + MultiMap allModules = MultiMap.create(); MultiMap relations = MultiMap.create(); Set transitiveEdges = new HashSet<>(); - Queue queue = new ArrayDeque<>(); GlobalSearchScope scope = ProjectScope.getAllScope(project); JavaModuleSearch.allModules(project, scope).forEach(module -> { - queue.add(module); + allModules.putValue(module.getName(), module); return true; }); - Set visited = new HashSet<>(); - while (!queue.isEmpty()) { - PsiJavaModule module = queue.poll(); - if (!(module instanceof LightJavaModule) && visited.add(module)) { - Set shouldBeVisited = visit(module, relations, transitiveEdges); - shouldBeVisited.removeAll(visited); - queue.addAll(shouldBeVisited); + for (PsiJavaModule module : allModules.values()) { + if (!(module instanceof LightJavaModule)) { + visit(module, relations, transitiveEdges, allModules); } } @@ -414,40 +412,50 @@ public final class JavaPsiModuleUtil { * the relations between modules, and the set of transitive edges based on the requires statements within * the module. * - * @param module the module to be visited - * @param relations a mapping that represents module dependencies + * @param module the module to be visited + * @param relations a mapping that represents module dependencies * @param transitiveEdges a set of transitive edges representing transitive dependencies - * @return a set of modules that should be visited next + * @param allModules a map of module names to PsiJavaModule instances */ - private static @NotNull Set visit(@NotNull PsiJavaModule module, - @NotNull MultiMap relations, - @NotNull Set transitiveEdges) { - Set shouldBeVisited = new HashSet<>(); - + private static void visit(@NotNull PsiJavaModule module, + @NotNull MultiMap relations, + @NotNull Set transitiveEdges, + @NotNull MultiMap allModules) { relations.putValues(module, Collections.emptyList()); boolean explicitJavaBase = false; + GlobalSearchScope scope = GlobalSearchScope.allScope(module.getProject()); for (PsiRequiresStatement statement : module.getRequires()) { PsiJavaModuleReference ref = statement.getModuleReference(); if (ref != null) { - if (JAVA_BASE.equals(ref.getCanonicalText())) explicitJavaBase = true; - for (ResolveResult result : ref.multiResolve(true)) { - PsiJavaModule dependency = (PsiJavaModule)result.getElement(); - assert dependency != null : result; + String moduleName = ref.getCanonicalText(); + if (JAVA_BASE.equals(moduleName)) explicitJavaBase = true; + for (PsiJavaModule dependency : filterModules(allModules.get(moduleName), scope)) { relations.putValue(module, dependency); if (statement.hasModifierProperty(PsiModifier.TRANSITIVE)) { transitiveEdges.add(RequiresGraph.key(dependency, module)); } - shouldBeVisited.add(dependency); } } } if (!explicitJavaBase) { - PsiJavaModule javaBase = JavaPsiFacade.getInstance(module.getProject()).findModule(JAVA_BASE, module.getResolveScope()); - if (javaBase != null) relations.putValue(module, javaBase); + Collection modules = filterModules(allModules.get(JAVA_BASE), module.getResolveScope()); + if (modules.size() == 1) { + relations.putValue(module, modules.iterator().next()); + } } - return shouldBeVisited; + } + + private static @NotNull List filterModules(@NotNull Collection modules, @NotNull GlobalSearchScope scope) { + SmartList filtered = new SmartList<>(); + for (PsiJavaModule candidate : modules) { + VirtualFile candidateFile = getVirtualFile(candidate); + if (candidateFile != null && scope.contains(candidateFile)) { + filtered.add(candidate); + } + } + return filtered; } private static final class ChameleonGraph implements Graph { diff --git a/java/java-analysis-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java b/java/java-analysis-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java index 0df48bd29ec7..bfacd21eac41 100644 --- a/java/java-analysis-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java +++ b/java/java-analysis-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java @@ -1,16 +1,17 @@ -// Copyright 2000-2022 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.file.impl; -import com.intellij.codeInsight.daemon.impl.analysis.ManifestUtil; import com.intellij.ide.highlighter.JavaClassFileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.PackageIndex; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Predicates; @@ -20,15 +21,10 @@ import com.intellij.psi.impl.PackagePrefixElementFinder; import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.impl.PsiManagerEx; import com.intellij.psi.impl.file.PsiPackageImpl; -import com.intellij.psi.impl.java.stubs.index.JavaAutoModuleNameIndex; import com.intellij.psi.impl.java.stubs.index.JavaFullClassNameIndex; -import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex; -import com.intellij.psi.impl.java.stubs.index.JavaSourceModuleNameIndex; -import com.intellij.psi.impl.light.LightJavaModule; import com.intellij.psi.search.DelegatingGlobalSearchScope; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.search.searches.JavaModuleSearch; import com.intellij.psi.util.JavaMultiReleaseUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -36,7 +32,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Predicate; -import java.util.jar.JarFile; import java.util.stream.Stream; import static java.util.Objects.requireNonNull; @@ -75,7 +70,7 @@ public final class JavaFileManagerImpl implements JavaFileManager, Disposable { int count = result.size(); if (count == 0) return PsiClass.EMPTY_ARRAY; - if (count == 1) return new PsiClass[] {result.get(0).getFirst()}; + if (count == 1) return new PsiClass[] {result.getFirst().getFirst()}; ContainerUtil.quickSort(result, (o1, o2) -> scope.compare(o2.getSecond(), o1.getSecond())); @@ -159,51 +154,7 @@ public final class JavaFileManagerImpl implements JavaFileManager, Disposable { @Override public @NotNull Collection findModules(@NotNull String moduleName, @NotNull GlobalSearchScope scope) { GlobalSearchScope excludingScope = new LibSrcExcludingScope(scope); - - Project project = myManager.getProject(); - List results = new ArrayList<>(JavaModuleNameIndex.getInstance().getModules(moduleName, project, excludingScope)); - - Set shadowedRoots = new HashSet<>(); - for (VirtualFile manifest : JavaSourceModuleNameIndex.getFilesByKey(moduleName, excludingScope)) { - VirtualFile root = manifest.getParent().getParent(); - shadowedRoots.add(root); - results.add(LightJavaModule.create(myManager, root, moduleName)); - } - - for (VirtualFile root : JavaAutoModuleNameIndex.getFilesByKey(moduleName, excludingScope)) { - if (shadowedRoots.contains(root)) { //already found by MANIFEST attribute - continue; - } - VirtualFile manifest = root.findFileByRelativePath(JarFile.MANIFEST_NAME); - if (manifest != null && LightJavaModule.claimedModuleName(manifest) != null) { - continue; - } - results.add(LightJavaModule.create(myManager, root, moduleName)); - } - - if (results.isEmpty()) { - CachedValuesManager valuesManager = CachedValuesManager.getManager(project); - ProjectRootModificationTracker rootModificationTracker = ProjectRootModificationTracker.getInstance(project); - for (Module module : ModuleManager.getInstance(project).getModules()) { - VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(false); - if (sourceRoots.length > 0) { - String virtualAutoModuleName = ManifestUtil.lightManifestAttributeValue(module, PsiJavaModule.AUTO_MODULE_NAME); - if (moduleName.equals(virtualAutoModuleName)) { - results.add(LightJavaModule.create(myManager, sourceRoots[0], moduleName)); - break; - } - - String defaultModuleName = valuesManager.getCachedValue(module, () -> - CachedValueProvider.Result.create(LightJavaModule.moduleName(module.getName()), rootModificationTracker) - ); - if (moduleName.equals(defaultModuleName)) { - results.add(LightJavaModule.create(myManager, sourceRoots[0], moduleName)); - break; - } - } - } - } - + Collection results = JavaModuleSearch.search(moduleName, myManager.getProject(), excludingScope).findAll(); return upgradeModules(sortModules(results, scope), moduleName, scope); } diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaModuleSearcher.java b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaModuleSearcher.java index 21825177b375..c6d5f54a5194 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaModuleSearcher.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaModuleSearcher.java @@ -1,52 +1,178 @@ // 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.search; +import com.intellij.java.codeserver.core.JavaManifestUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ProjectRootModificationTracker; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiJavaModule; -import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys; +import com.intellij.psi.PsiManager; +import com.intellij.psi.impl.java.stubs.index.JavaAutoModuleNameIndex; +import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex; +import com.intellij.psi.impl.java.stubs.index.JavaSourceModuleNameIndex; +import com.intellij.psi.impl.light.LightJavaModule; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.JavaModuleSearch; -import com.intellij.psi.stubs.StubIndex; -import com.intellij.util.CommonProcessors.CollectProcessor; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; import com.intellij.util.Processor; import com.intellij.util.QueryExecutor; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.jar.JarFile; public final class JavaModuleSearcher implements QueryExecutor { @Override public boolean execute(JavaModuleSearch.@NotNull Parameters queryParameters, @NotNull Processor consumer) { - String name = queryParameters.getName(); - StubIndex index = StubIndex.getInstance(); - if (name == null) { - //It is important to collect moduleNames first, then process the name -- don't do it recursively! -- it risks - // a deadlock: processAllKeys() acquires readLock, but processElements() _could_ acquire writeLock (see - // StubIndexEx.tryFixIndexesForProblemFiles()) - //In general: it is a bad idea to do recursive index lookups, i.e., another lookup from the lambda passed to something - // like processAllKeys()/processElements(). Such lambdas should be a short & simple code, not complex deep-stack processing. - // In a second case -- 'unfold' the recursive processing, as it is done here. - CollectProcessor moduleNamesCollector = new CollectProcessor<>(); - index.processAllKeys(JavaStubIndexKeys.MODULE_NAMES, moduleNamesCollector, queryParameters.getScope()); - for (String moduleName : moduleNamesCollector.getResults()) { - boolean shouldContinue = index.processElements( - JavaStubIndexKeys.MODULE_NAMES, - moduleName, - queryParameters.getProject(), - queryParameters.getScope(), - null, - PsiJavaModule.class, - consumer - ); - if (!shouldContinue) { - return false; - } + String moduleName = queryParameters.getName(); + Project project = queryParameters.getProject(); + GlobalSearchScope scope = queryParameters.getScope(); + + if (moduleName == null) { + return processAllModules(project, consumer); + } + + return processModuleByName(moduleName, project, scope, consumer); + } + + private static boolean processAllModules(@NotNull Project project, + @NotNull Processor consumer) { + GlobalSearchScope indexScope = GlobalSearchScope.allScope(project); + + // collect all module-name keys + Set allNames = new LinkedHashSet<>(); + allNames.addAll(JavaModuleNameIndex.getInstance().getAllKeys(project)); + allNames.addAll(JavaSourceModuleNameIndex.getAllKeys(project)); + allNames.addAll(JavaAutoModuleNameIndex.getAllKeys(project)); + + Set namesWithResults = new HashSet<>(); + // process real and indexed light modules only. + for (String name : allNames) { + if (!processModulesFromIndices(name, project, indexScope, consumer, namesWithResults)) { + return false; } + } + + return processJpsModules(project, consumer, namesWithResults, null); + } + + private static boolean processModuleByName(@NotNull String moduleName, + @NotNull Project project, + @NotNull GlobalSearchScope scope, + @NotNull Processor consumer) { + Set namesWithResults = new HashSet<>(); + + if (!processModulesFromIndices(moduleName, project, scope, consumer, namesWithResults)) { + return false; + } + + // If we already found the module, no need to fallback. + if (namesWithResults.contains(moduleName)) { return true; } - return index.processElements(JavaStubIndexKeys.MODULE_NAMES, - name, - queryParameters.getProject(), - queryParameters.getScope(), - null, - PsiJavaModule.class, - consumer); + + return processJpsModules(project, consumer, namesWithResults, moduleName); } -} + + private static boolean processJpsModules(@NotNull Project project, + @NotNull Processor consumer, + @NotNull Set namesWithResults, + @Nullable String moduleName) { + PsiManager psiManager = PsiManager.getInstance(project); + CachedValuesManager valuesManager = CachedValuesManager.getManager(project); + ProjectRootModificationTracker tracker = ProjectRootModificationTracker.getInstance(project); + Module[] modules = ModuleManager.getInstance(project).getModules(); + + for (Module module : modules) { + VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(false); + if (sourceRoots.length == 0) continue; + + VirtualFile root = sourceRoots[0]; + + // auto module name from manifest (including virtual manifests) + String autoModuleName = JavaManifestUtil.getManifestAttributeValue(module, PsiJavaModule.AUTO_MODULE_NAME); + if (autoModuleName != null && !namesWithResults.contains(autoModuleName)) { + if (moduleName != null && moduleName.equals(autoModuleName)) { + namesWithResults.add(autoModuleName); + if (!consumer.process(LightJavaModule.create(psiManager, root, autoModuleName))) return false; + return true; + } + else if (moduleName == null) { + namesWithResults.add(autoModuleName); + if (!consumer.process(LightJavaModule.create(psiManager, root, autoModuleName))) return false; + continue; + } + } + + // default module name derived from module name + String defaultModuleName = valuesManager.getCachedValue(module, () -> + CachedValueProvider.Result.create(LightJavaModule.moduleName(module.getName()), tracker)); + if (!namesWithResults.contains(defaultModuleName)) { + if (moduleName != null && moduleName.equals(defaultModuleName)) { + namesWithResults.add(defaultModuleName); + if (!consumer.process(LightJavaModule.create(psiManager, root, defaultModuleName))) return false; + return true; + } + else if (moduleName == null) { + namesWithResults.add(defaultModuleName); + if (!consumer.process(LightJavaModule.create(psiManager, root, defaultModuleName))) return false; + } + } + } + return true; + } + + private static boolean processModulesFromIndices(@NotNull String moduleName, + @NotNull Project project, + @NotNull GlobalSearchScope scope, + @NotNull Processor consumer, + @NotNull Set namesWithResults) { + PsiManager psiManager = PsiManager.getInstance(project); + // Real modules from module-info.java + for (PsiJavaModule module : JavaModuleNameIndex.getInstance().getModules(moduleName, project, scope)) { + namesWithResults.add(moduleName); + if (!consumer.process(module)) return false; + } + + // Light modules created from source manifests + Set shadowedRoots = new HashSet<>(); + for (VirtualFile manifest : JavaSourceModuleNameIndex.getFilesByKey(moduleName, scope)) { + VirtualFile root = getSourceRootFromManifest(manifest); + if (root == null) continue; + + namesWithResults.add(moduleName); + shadowedRoots.add(root); + + if (!consumer.process(LightJavaModule.create(psiManager, root, moduleName))) return false; + } + + // Light modules created from auto-module-name (jar roots) + for (VirtualFile root : JavaAutoModuleNameIndex.getFilesByKey(moduleName, scope)) { + if (shadowedRoots.contains(root)) continue; + + VirtualFile manifest = root.findFileByRelativePath(JarFile.MANIFEST_NAME); + // If the manifest claims a module name (possibly different), skip this root. + if (manifest != null && LightJavaModule.claimedModuleName(manifest) != null) continue; + + namesWithResults.add(moduleName); + if (!consumer.process(LightJavaModule.create(psiManager, root, moduleName))) return false; + } + return true; + } + + private static @Nullable VirtualFile getSourceRootFromManifest(@NotNull VirtualFile manifest) { + VirtualFile parent = manifest.getParent(); + if (parent == null) return null; + VirtualFile root = parent.getParent(); + if (root == null) return null; + return root; + } +} \ No newline at end of file