[java] upgrade module path: resolve (IDEA-189693)

Uses relative location of files on module dependency list to disambiguate upgradeable modules.
This commit is contained in:
Roman Shevchenko
2018-07-19 14:51:58 +02:00
parent 8214151117
commit c1340c48ad
7 changed files with 78 additions and 34 deletions
@@ -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<PsiJavaModule> findModules(@NotNull String moduleName, @NotNull GlobalSearchScope scope) {
scope = new LibSrcExcludingScope(scope);
GlobalSearchScope excludingScope = new LibSrcExcludingScope(scope);
Collection<PsiJavaModule> named = JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), scope);
Collection<PsiJavaModule> named = JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), excludingScope);
if (!named.isEmpty()) {
return named;
return upgradeModules(sortModules(named, scope), moduleName, scope);
}
Collection<VirtualFile> jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, scope);
Collection<VirtualFile> jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, excludingScope);
if (!jars.isEmpty()) {
List<PsiJavaModule> 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<PsiJavaModule> sortModules(Collection<PsiJavaModule> modules, GlobalSearchScope scope) {
if (modules.size() > 1) {
List<PsiJavaModule> 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<PsiJavaModule> upgradeModules(Collection<PsiJavaModule> 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<PsiJavaModule> list = new ArrayList<>(modules);
ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
for (ListIterator<PsiJavaModule> 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;
}
}
@@ -33,7 +33,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
fun testRequiresBare() =
variants("module M { requires <caret>",
"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<caret> }", "module M { requires transitive <caret> }")
fun testRequiresSimpleName() = complete("module M { requires M<caret> }", "module M { requires M2;<caret> }")
@@ -46,7 +46,7 @@ class ModuleCompletionTest : LightJava9ModulesCodeInsightFixtureTestCase() {
fun testExportsTo() = complete("module M { exports pkg.other <caret> }", "module M { exports pkg.other to <caret> }")
fun testExportsToList() =
variants("module M { exports pkg.other to <caret> }",
"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<caret> }", "module M { exports pkg.other to M2<caret> }")
fun testUsesPrefixed() = complete("module M { uses p<caret> }", "module M { uses pkg.<caret> }")
@@ -321,6 +321,15 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
""".trimIndent())
}
fun testUpgradeableModuleOnModulePath() {
myFixture.enableInspections(DeprecationInspection(), MarkedForRemovalInspection())
highlight("""
module M {
requires <error descr="'java.xml.bind' is deprecated and marked for removal">java.xml.bind</error>;
requires java.xml.ws;
}""".trimIndent())
}
fun testLinearModuleGraphBug() {
addFile("module-info.java", "module M6 { requires M7; }", M6)
addFile("module-info.java", "module M7 { }", M7)
@@ -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())
}
}
}
Binary file not shown.
@@ -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<Module> 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<VirtualFile> roots = ContainerUtil.newLinkedHashSet();
Set<VirtualFile> roots = ContainerUtil.newLinkedHashSet();
if (hasOption(CONTENT)) {
Set<Module> 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<Module> 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<Module> modules = ContainerUtil.newLinkedHashSet();
Set<Module> 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);
}
}
}