diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java b/java/java-indexing-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java index b005fa82a265..a22aadcd0680 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/file/impl/JavaFileManagerImpl.java @@ -5,6 +5,8 @@ import com.intellij.ProjectTopics; import com.intellij.ide.highlighter.JavaClassFileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.impl.scopes.ModuleWithDependenciesScope; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.util.Comparing; @@ -28,6 +30,7 @@ import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static java.util.Objects.requireNonNull; @@ -172,18 +175,18 @@ public class JavaFileManagerImpl implements JavaFileManager, Disposable { @NotNull @Override public Collection findModules(@NotNull String moduleName, @NotNull GlobalSearchScope scope) { - scope = new LibSrcExcludingScope(scope); + GlobalSearchScope excludingScope = new LibSrcExcludingScope(scope); - Collection named = JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), scope); + Collection named = JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), excludingScope); if (!named.isEmpty()) { - return named; + return upgradeModules(sortModules(named, scope), moduleName, scope); } - Collection jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, scope); + Collection jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, excludingScope); if (!jars.isEmpty()) { List automatic = jars.stream().map(f -> LightJavaModule.getModule(myManager, f)).collect(Collectors.toList()); if (!automatic.isEmpty()) { - return automatic; + return sortModules(automatic, scope); } } @@ -203,4 +206,49 @@ public class JavaFileManagerImpl implements JavaFileManager, Disposable { return super.contains(file) && !myIndex.isInLibrarySource(file); } } + + private static Collection sortModules(Collection modules, GlobalSearchScope scope) { + if (modules.size() > 1) { + List list = new ArrayList<>(modules); + list.sort((m1, m2) -> scope.compare(virtualFile(m2), virtualFile(m1))); + modules = list; + } + return modules; + } + + private static VirtualFile virtualFile(PsiJavaModule m) { + return m instanceof LightJavaModule ? ((LightJavaModule)m).getRootVirtualFile() : m.getContainingFile().getVirtualFile(); + } + + private static Collection upgradeModules(Collection modules, String moduleName, GlobalSearchScope scope) { + if (modules.size() > 1 && PsiJavaModule.UPGRADEABLE.contains(moduleName) && scope instanceof ModuleWithDependenciesScope) { + Module module = ((ModuleWithDependenciesScope)scope).getModule(); + boolean isModular = Stream.of(ModuleRootManager.getInstance(module).getSourceRoots(true)) + .filter(scope::contains) + .anyMatch(root -> root.findChild(PsiJavaModule.MODULE_INFO_FILE) != null); + if (isModular) { + List list = new ArrayList<>(modules); + + ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex(); + for (ListIterator i = list.listIterator(); i.hasNext(); ) { + PsiJavaModule candidate = i.next(); + if (index.getOrderEntryForFile(candidate.getContainingFile().getVirtualFile()) instanceof JdkOrderEntry) { + if (i.previousIndex() > 0) { + i.remove(); // not at the top -> is upgraded + } + else { + list = Collections.singletonList(candidate); // shadows subsequent modules + break; + } + } + } + + if (list.size() != modules.size()) { + modules = list; + } + } + } + + return modules; + } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/jigsaw/lib-xml-ws.jar b/java/java-tests/testData/codeInsight/jigsaw/lib-xml-ws.jar new file mode 100644 index 000000000000..03ffc3e087b7 Binary files /dev/null and b/java/java-tests/testData/codeInsight/jigsaw/lib-xml-ws.jar differ diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt index 952856610c76..93a07cd3ff94 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/ModuleCompletionTest.kt @@ -33,7 +33,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() { fun testRequiresBare() = variants("module M { requires ", - "transitive", "static", "M2", "java.base", "java.se", "java.xml.bind", "javax.doomed", + "transitive", "static", "M2", "java.base", "java.se", "java.xml.bind", "java.xml.ws", "javax.doomed", "lib.multi.release", "lib.named", "lib.auto", "lib.claimed") fun testRequiresTransitive() = complete("module M { requires tr }", "module M { requires transitive }") fun testRequiresSimpleName() = complete("module M { requires M }", "module M { requires M2; }") @@ -46,7 +46,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() { fun testExportsTo() = complete("module M { exports pkg.other }", "module M { exports pkg.other to }") fun testExportsToList() = variants("module M { exports pkg.other to }", - "M2", "java.base", "java.se", "java.xml.bind", "javax.doomed", "lib.multi.release", "lib.named") + "M2", "java.base", "java.se", "java.xml.bind", "java.xml.ws", "javax.doomed", "lib.multi.release", "lib.named") fun testExportsToUnambiguous() = complete("module M { exports pkg.other to M }", "module M { exports pkg.other to M2 }") fun testUsesPrefixed() = complete("module M { uses p }", "module M { uses pkg. }") diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt index acb0f99d876a..5619c0963256 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt @@ -321,6 +321,15 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { """.trimIndent()) } + fun testUpgradeableModuleOnModulePath() { + myFixture.enableInspections(DeprecationInspection(), MarkedForRemovalInspection()) + highlight(""" + module M { + requires java.xml.bind; + requires java.xml.ws; + }""".trimIndent()) + } + fun testLinearModuleGraphBug() { addFile("module-info.java", "module M6 { requires M7; }", M6) addFile("module-info.java", "module M7 { }", M7) diff --git a/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleJava9ProjectDescriptor.kt b/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleJava9ProjectDescriptor.kt index d037b9981a9c..7fdf715b8f77 100644 --- a/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleJava9ProjectDescriptor.kt +++ b/java/java-tests/testSrc/com/intellij/java/testFramework/fixtures/MultiModuleJava9ProjectDescriptor.kt @@ -74,6 +74,14 @@ object MultiModuleJava9ProjectDescriptor : DefaultLightProjectDescriptor() { ModuleRootModificationUtil.addModuleLibrary(main, "${libDir}/lib-multi-release.jar!/") ModuleRootModificationUtil.addModuleLibrary(main, "${libDir}/lib_invalid_1_2.jar!/") ModuleRootModificationUtil.addModuleLibrary(main, "${libDir}/lib-xml-bind.jar!/") + + ModuleRootModificationUtil.addModuleLibrary(main, "${libDir}/lib-xml-ws.jar!/") + ModuleRootModificationUtil.updateModel(main) { + val entries = it.orderEntries.toMutableList() + entries.add(0, entries.last()) // places an upgrade module before the JDK + entries.removeAt(entries.size - 1) + it.rearrangeOrderEntries(entries.toTypedArray()) + } } } diff --git a/java/mockJDK-1.9/jre/lib/java.xml.ws.jar b/java/mockJDK-1.9/jre/lib/java.xml.ws.jar new file mode 100644 index 000000000000..9e4e323e8b92 Binary files /dev/null and b/java/mockJDK-1.9/jre/lib/java.xml.ws.jar differ diff --git a/platform/indexing-impl/src/com/intellij/openapi/module/impl/scopes/ModuleWithDependenciesScope.java b/platform/indexing-impl/src/com/intellij/openapi/module/impl/scopes/ModuleWithDependenciesScope.java index 9b66901aca47..03a89ffec56d 100644 --- a/platform/indexing-impl/src/com/intellij/openapi/module/impl/scopes/ModuleWithDependenciesScope.java +++ b/platform/indexing-impl/src/com/intellij/openapi/module/impl/scopes/ModuleWithDependenciesScope.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.openapi.module.impl.scopes; import com.intellij.openapi.module.Module; @@ -44,9 +30,7 @@ public class ModuleWithDependenciesScope extends GlobalSearchScope { @interface ScopeConstant {} private final Module myModule; - @ScopeConstant - private final int myOptions; - + private final @ScopeConstant int myOptions; private final ProjectFileIndexImpl myProjectFileIndex; private volatile Set myModules; @@ -56,11 +40,9 @@ public class ModuleWithDependenciesScope extends GlobalSearchScope { super(module.getProject()); myModule = module; myOptions = options; - myProjectFileIndex = (ProjectFileIndexImpl)ProjectRootManager.getInstance(module.getProject()).getFileIndex(); - final LinkedHashSet roots = ContainerUtil.newLinkedHashSet(); - + Set roots = ContainerUtil.newLinkedHashSet(); if (hasOption(CONTENT)) { Set modules = calcModules(); myModules = ContainerUtil.newTroveSet(modules); @@ -87,10 +69,7 @@ public class ModuleWithDependenciesScope extends GlobalSearchScope { private OrderEnumerator getOrderEnumeratorForOptions() { OrderEnumerator en = ModuleRootManager.getInstance(myModule).orderEntries(); en.recursively(); - - if (hasOption(COMPILE_ONLY)) { - en.exportedOnly().compileOnly(); - } + if (hasOption(COMPILE_ONLY)) en.exportedOnly().compileOnly(); if (!hasOption(LIBRARIES)) en.withoutLibraries().withoutSdk(); if (!hasOption(MODULES)) en.withoutDepModules(); if (!hasOption(TESTS)) en.productionOnly(); @@ -101,7 +80,7 @@ public class ModuleWithDependenciesScope extends GlobalSearchScope { private Set calcModules() { // In the case that hasOption(CONTENT), the order of the modules set matters for // ordering the content roots, so use a LinkedHashSet - final Set modules = ContainerUtil.newLinkedHashSet(); + Set modules = ContainerUtil.newLinkedHashSet(); OrderEnumerator en = getOrderEnumeratorForOptions(); en.forEach(each -> { if (each instanceof ModuleOrderEntry) { @@ -215,4 +194,4 @@ public class ModuleWithDependenciesScope extends GlobalSearchScope { " include other modules:" + hasOption(MODULES) + " include tests:" + hasOption(TESTS); } -} +} \ No newline at end of file