[java] automatic modules

This commit is contained in:
Roman Shevchenko
2016-10-14 19:08:29 +02:00
parent 7836eb6a6d
commit 156a98a67b
14 changed files with 380 additions and 34 deletions
@@ -28,8 +28,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightJavaModule;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
@@ -62,11 +64,21 @@ public class ModuleHighlightUtil {
Project project = fsItem.getProject();
ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(project);
if (index.isInLibraryClasses(file)) {
return Optional.ofNullable(index.getClassRootForFile(file))
.map(r -> r.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE))
.map(PsiManager.getInstance(project)::findFile)
.map(f -> f instanceof PsiJavaFile ? ((PsiJavaFile)f).getModuleDeclaration() : null)
.orElse(null);
VirtualFile classRoot = index.getClassRootForFile(file);
if (classRoot != null) {
VirtualFile descriptorFile = classRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE);
if (descriptorFile != null) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(descriptorFile);
if (psiFile instanceof PsiJavaFile) {
return ((PsiJavaFile)psiFile).getModuleDeclaration();
}
}
else if (classRoot.getFileSystem() instanceof JarFileSystem && "jar".equalsIgnoreCase(classRoot.getExtension())) {
return LightJavaModule.getModule(PsiManager.getInstance(project), classRoot);
}
}
return null;
}
else {
Module module = index.getModuleForFile(file);
@@ -374,7 +386,7 @@ public class ModuleHighlightUtil {
String refModuleName = refModule.getModuleName();
String requiredName = targetModule.getModuleName();
if (!JavaModuleGraphUtil.exports(targetModule, packageName, refModule)) {
if (!(targetModule instanceof LightJavaModule || JavaModuleGraphUtil.exports(targetModule, packageName, refModule))) {
String message = JavaErrorMessages.message("module.package.not.exported", requiredName, packageName, refModuleName);
return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(ref).description(message).create();
}
@@ -729,7 +729,7 @@ public class JavaDocInfoGenerator {
private void generateModuleJavaDoc(StringBuilder buffer, PsiJavaModule module, boolean generatePrologueAndEpilogue) {
if (generatePrologueAndEpilogue) generatePrologue(buffer);
buffer.append("<pre>module <b>").append(module.getName()).append("</b></pre>");
buffer.append("<pre>module <b>").append(module.getModuleName()).append("</b></pre>");
PsiDocComment comment = module.getDocComment();
if (comment != null) {
@@ -33,12 +33,10 @@ import com.intellij.lang.documentation.ExternalDocumentationProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
@@ -47,6 +45,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileSystem;
import com.intellij.psi.*;
import com.intellij.psi.impl.beanProperties.BeanPropertyElement;
import com.intellij.psi.impl.light.LightJavaModule;
import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.javadoc.PsiDocComment;
@@ -141,7 +140,6 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
private static void generateModifiers(StringBuilder buffer, PsiElement element) {
String modifiers = PsiFormatUtil.formatModifiers(element, PsiFormatUtilBase.JAVADOC_MODIFIERS_ONLY);
if (modifiers.length() > 0) {
buffer.append(modifiers);
buffer.append(" ");
@@ -154,20 +152,9 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
private static void generateOrderEntryAndPackageInfo(StringBuilder buffer, @NotNull PsiElement element) {
PsiFile file = element.getContainingFile();
ProjectFileIndex fileIndex = ProjectRootManager.getInstance(element.getProject()).getFileIndex();
VirtualFile vFile = file.getVirtualFile();
if (vFile != null && (fileIndex.isInLibrarySource(vFile) || fileIndex.isInLibraryClasses(vFile))) {
final List<OrderEntry> orderEntries = fileIndex.getOrderEntriesForFile(vFile);
OrderEntry orderEntry = ContainerUtil.find(orderEntries, Conditions.instanceOf(LibraryOrSdkOrderEntry.class));
if (orderEntry != null) {
buffer.append("[").append(StringUtil.escapeXml(orderEntry.getPresentableName())).append("] ");
}
}
else {
final Module module = ModuleUtilCore.findModuleForPsiElement(file);
if (module != null) {
buffer.append('[').append(module.getName()).append("] ");
}
if (file != null) {
generateOrderEntryInfo(buffer, file.getVirtualFile(), element.getProject());
}
if (file instanceof PsiJavaFile) {
@@ -179,6 +166,23 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
}
}
private static void generateOrderEntryInfo(StringBuilder buffer, VirtualFile file, Project project) {
if (file != null) {
ProjectFileIndex index = ProjectRootManager.getInstance(project).getFileIndex();
if (index.isInLibrarySource(file) || index.isInLibraryClasses(file)) {
index.getOrderEntriesForFile(file).stream()
.filter(LibraryOrSdkOrderEntry.class::isInstance).findFirst()
.ifPresent(entry -> buffer.append('[').append(StringUtil.escapeXml(entry.getPresentableName())).append("] "));
}
else {
Module module = index.getModuleForFile(file);
if (module != null) {
buffer.append('[').append(module.getName()).append("] ");
}
}
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static String generateClassInfo(PsiClass aClass) {
StringBuilder buffer = new StringBuilder();
@@ -376,8 +380,21 @@ public class JavaDocumentationProvider extends DocumentationProviderEx implement
private static String generateModuleInfo(PsiJavaModule module) {
StringBuilder sb = new StringBuilder();
generateOrderEntryAndPackageInfo(sb, module);
VirtualFile file = null;
if (module instanceof LightJavaModule) {
PsiElement target = module.getNavigationElement();
if (target instanceof PsiDirectory) {
file = ((PsiDirectory)target).getVirtualFile();
}
}
else {
file = module.getContainingFile().getVirtualFile();
}
generateOrderEntryInfo(sb, file, module.getProject());
sb.append(LangBundle.message("java.terms.module")).append(' ').append(module.getModuleName());
return sb.toString();
}
@@ -28,8 +28,10 @@ import com.intellij.openapi.vfs.VirtualFileWithId;
import com.intellij.psi.*;
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.light.LightJavaModule;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
@@ -38,6 +40,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author dmitry lomov
@@ -180,6 +183,19 @@ public class JavaFileManagerImpl implements JavaFileManager, Disposable {
@NotNull
@Override
public Collection<PsiJavaModule> findModules(@NotNull String moduleName, @NotNull GlobalSearchScope scope) {
return JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), scope);
Collection<PsiJavaModule> named = JavaModuleNameIndex.getInstance().get(moduleName, myManager.getProject(), scope);
if (!named.isEmpty()) {
return named;
}
Collection<VirtualFile> jars = JavaAutoModuleNameIndex.getFilesByKey(moduleName, scope);
if (!jars.isEmpty()) {
List<PsiJavaModule> automatic = jars.stream().map(f -> LightJavaModule.getModule(myManager, f)).collect(Collectors.toList());
if (!automatic.isEmpty()) {
return automatic;
}
}
return Collections.emptyList();
}
}
@@ -0,0 +1,77 @@
/*
* 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.
*/
package com.intellij.psi.impl.java.stubs.index;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.impl.light.LightJavaModule;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.indexing.*;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import static java.util.Collections.singletonMap;
public class JavaAutoModuleNameIndex extends ScalarIndexExtension<String> {
private static final ID<String, Void> NAME = ID.create("java.auto.module.name");
private final FileBasedIndex.InputFilter myFilter =
file -> file.isDirectory() && file.getParent() == null && "jar".equalsIgnoreCase(file.getExtension());
private final DataIndexer<String, Void, FileContent> myIndexer =
data -> singletonMap(LightJavaModule.moduleName(data.getFile().getNameWithoutExtension()), null);
@NotNull
@Override
public ID<String, Void> getName() {
return NAME;
}
@Override
public int getVersion() {
return 1 + (FileBasedIndex.ourEnableTracingOfKeyHashToVirtualFileMapping ? 2 : 0);
}
@NotNull
@Override
public KeyDescriptor<String> getKeyDescriptor() {
return EnumeratorStringDescriptor.INSTANCE;
}
@Override
public boolean dependsOnFileContent() {
return false;
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return myFilter;
}
@NotNull
@Override
public DataIndexer<String, Void, FileContent> getIndexer() {
return myIndexer;
}
@NotNull
public static Collection<VirtualFile> getFilesByKey(@NotNull String moduleName, @NotNull GlobalSearchScope scope) {
return FileBasedIndex.getInstance().getContainingFiles(NAME, moduleName, scope);
}
}
@@ -0,0 +1,189 @@
/*
* 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.
*/
package com.intellij.psi.impl.light;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootModificationTracker;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.CachedValueProvider.Result;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.ParameterizedCachedValue;
import com.intellij.psi.util.ParameterizedCachedValueProvider;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.openapi.util.Pair.pair;
import static com.intellij.psi.util.PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT;
import static com.intellij.util.ObjectUtils.notNull;
public class LightJavaModule extends LightElement implements PsiJavaModule {
private final LightJavaModuleReferenceElement myRefElement;
private final VirtualFile myJarRoot;
private LightJavaModule(@NotNull PsiManager manager, @NotNull VirtualFile jarRoot) {
super(manager, JavaLanguage.INSTANCE);
myJarRoot = jarRoot;
myRefElement = new LightJavaModuleReferenceElement(manager, moduleName(jarRoot.getNameWithoutExtension()));
}
@Nullable
@Override
public PsiDocComment getDocComment() {
return null;
}
@NotNull
@Override
public PsiJavaModuleReferenceElement getNameElement() {
return myRefElement;
}
@NotNull
@Override
public String getModuleName() {
return myRefElement.getReferenceText();
}
@NotNull
@Override
public Iterable<PsiRequiresStatement> getRequires() {
return Collections.emptyList();
}
@NotNull
@Override
public Iterable<PsiExportsStatement> getExports() {
return Collections.emptyList();
}
@Override
public String getName() {
return getModuleName();
}
@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
throw new IncorrectOperationException("Cannot modify automatic module '" + getName() + "'");
}
@NotNull
@Override
public PsiElement getNavigationElement() {
return notNull(myManager.findDirectory(myJarRoot), super.getNavigationElement());
}
@Override
public boolean equals(Object obj) {
return obj instanceof LightJavaModule && myJarRoot.equals(((LightJavaModule)obj).myJarRoot) && getManager() == ((LightJavaModule)obj).getManager();
}
@Override
public int hashCode() {
return getModuleName().hashCode() * 31 + getManager().hashCode();
}
@Override
public String toString() {
return "PsiJavaModule:" + getModuleName();
}
private static class LightJavaModuleReferenceElement extends LightElement implements PsiJavaModuleReferenceElement {
private final String myText;
public LightJavaModuleReferenceElement(@NotNull PsiManager manager, @NotNull String text) {
super(manager, JavaLanguage.INSTANCE);
myText = text;
}
@NotNull
@Override
public String getReferenceText() {
return myText;
}
@Nullable
@Override
public PsiPolyVariantReference getReference() {
return null;
}
@Override
public String toString() {
return "PsiJavaModuleReference";
}
}
@NotNull
public static LightJavaModule getModule(@NotNull PsiManager manager, @NotNull VirtualFile jarRoot) {
Project project = manager.getProject();
CachedValuesManager cache = CachedValuesManager.getManager(project);
return cache.getParameterizedCachedValue(project, KEY, new ParameterizedCachedValueProvider<LightJavaModule, Pair<PsiManager, VirtualFile>>() {
@Nullable
@Override
public Result<LightJavaModule> compute(Pair<PsiManager, VirtualFile> p) {
LightJavaModule module = new LightJavaModule(p.first, p.second);
return Result.create(module, OUT_OF_CODE_BLOCK_MODIFICATION_COUNT, ProjectRootModificationTracker.getInstance(p.first.getProject()));
}
}, false, pair(manager, jarRoot));
}
private static final Key<ParameterizedCachedValue<LightJavaModule, Pair<PsiManager, VirtualFile>>> KEY = Key.create("java.light.module");
/**
* Implements a name deriving for automatic modules as described in ModuleFinder.of(Path...) method documentation.
*
* @param name a .jar file name without extension
* @see <a href="http://download.java.net/java/jigsaw/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-">ModuleFinder.of(Path...)</a>
*/
@NotNull
public static String moduleName(@NotNull String name) {
// If the name matches the regular expression "-(\\d+(\\.|$))" then the module name will be derived from the sub-sequence
// preceding the hyphen of the first occurrence.
Matcher m = Patterns.VERSION.matcher(name);
if (m.find()) {
name = name.substring(0, m.start());
}
// For the module name, then any trailing digits and dots are removed ...
name = Patterns.TAIL_VERSION.matcher(name).replaceFirst("");
// ... all non-alphanumeric characters ([^A-Za-z0-9]) are replaced with a dot (".") ...
name = Patterns.NON_NAME.matcher(name).replaceAll(".");
// ... all repeating dots are replaced with one dot ...
name = Patterns.DOT_SEQUENCE.matcher(name).replaceAll(".");
// ... and all leading and trailing dots are removed
name = StringUtil.trimLeading(StringUtil.trimTrailing(name, '.'), '.');
return name;
}
private static class Patterns {
private static final Pattern VERSION = Pattern.compile("-(\\d+(\\.|$))");
private static final Pattern TAIL_VERSION = Pattern.compile("[0-9.]+$");
private static final Pattern NON_NAME = Pattern.compile("[^A-Za-z0-9]");
private static final Pattern DOT_SEQUENCE = Pattern.compile("\\.{2,}");
}
}
@@ -28,7 +28,7 @@ import com.intellij.psi.*;
import com.intellij.psi.impl.file.impl.JavaFileManager;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValueProvider.Result;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.ParameterizedCachedValue;
import com.intellij.psi.util.ParameterizedCachedValueProvider;
@@ -131,10 +131,10 @@ public class PsiJavaModuleReference extends PsiReferenceBase.Poly<PsiJavaModuleR
return manager.getParameterizedCachedValue(refOwner, KEY, new ParameterizedCachedValueProvider<PsiJavaModule, Pair<String, Boolean>>() {
@Nullable
@Override
public CachedValueProvider.Result<PsiJavaModule> compute(Pair<String, Boolean> p) {
public Result<PsiJavaModule> compute(Pair<String, Boolean> p) {
Collection<PsiJavaModule> modules = Resolver.findModules(refOwner.getContainingFile(), p.first, p.second);
PsiJavaModule module = modules.size() == 1 ? modules.iterator().next() : null;
return CachedValueProvider.Result.create(module, OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
return Result.create(module, OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
}, false, pair(refText, incompleteCode));
}
Binary file not shown.
@@ -134,7 +134,7 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
}
fun testPackageAccessibility() {
addFile("module-info.java", "module M { requires M2; requires M6; requires LIB1; }")
addFile("module-info.java", "module M { requires M2; requires M6; requires lib.named; requires lib.auto; }")
addFile("module-info.java", "module M2 { exports pkg.m2; exports pkg.m2.impl to close.friends.only; }", M2)
addFile("pkg/m2/C2.java", "package pkg.m2;\npublic class C2 { }", M2)
addFile("pkg/m2/impl/C2Impl.java", "package pkg.m2.impl;\nimport pkg.m2.C2;\npublic class C2Impl { public static C2 make() {} }", M2)
@@ -155,8 +155,11 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() {
import pkg.m7.C7;
import pkg.lib1.LC1;
import <error descr="The module 'LIB1' does not export the package 'pkg.lib1.impl' to the module 'M'">pkg.lib1.impl.LC1Impl</error>;
import <error descr="The module 'LIB1' does not export the package 'pkg.lib1.impl' to the module 'M'">pkg.lib1.impl</error>.*;
import <error descr="The module 'lib.named' does not export the package 'pkg.lib1.impl' to the module 'M'">pkg.lib1.impl.LC1Impl</error>;
import <error descr="The module 'lib.named' does not export the package 'pkg.lib1.impl' to the module 'M'">pkg.lib1.impl</error>.*;
import pkg.lib2.LC2;
import pkg.lib2.impl.LC2Impl;
import static <error descr="The module 'M2' does not export the package 'pkg.m2.impl' to the module 'M'">pkg.m2.impl.C2Impl</error>.make;
@@ -0,0 +1,30 @@
/*
* 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.
*/
package com.intellij.psi.impl.light
import org.junit.Test
import kotlin.test.assertEquals
class JavaModuleNameDetectionTest {
@Test fun plain() = doTest("foo", "foo")
@Test fun versioned() = doTest("foo-1.2.3-SNAPSHOT", "foo")
@Test fun trailing() = doTest("foo2bar1.2.3", "foo2bar")
@Test fun replacing() = doTest("foo_bar", "foo.bar")
@Test fun collapsing() = doTest("foo...bar", "foo.bar")
@Test fun trimming() = doTest("...foo.bar...", "foo.bar")
private fun doTest(original: String, expected: String) = assertEquals(expected, LightJavaModule.moduleName(original))
}
@@ -71,8 +71,9 @@ object MultiModuleJava9ProjectDescriptor : DefaultLightProjectDescriptor() {
val m7 = makeModule(project, ModuleDescriptor.M7)
ModuleRootModificationUtil.addDependency(m6, m7, DependencyScope.COMPILE, true)
val libDir = "jar://${PathManagerEx.getTestDataPath()}/codeInsight/jigsaw/"
ModuleRootModificationUtil.addModuleLibrary(main, libDir + "lib1.jar!/")
val libDir = "jar://${PathManagerEx.getTestDataPath()}/codeInsight/jigsaw"
ModuleRootModificationUtil.addModuleLibrary(main, "${libDir}/lib-named-1.0.jar!/")
ModuleRootModificationUtil.addModuleLibrary(main, "${libDir}/lib-auto-1.0.jar!/")
}
}
+1
View File
@@ -1600,6 +1600,7 @@
<fileBasedIndex implementation="com.intellij.psi.impl.java.JavaFunctionalExpressionIndex"/>
<fileBasedIndex implementation="com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisIndex"/>
<fileBasedIndex implementation="com.intellij.psi.RefQueueIndex"/>
<fileBasedIndex implementation="com.intellij.psi.impl.java.stubs.index.JavaAutoModuleNameIndex"/>
<stubElementTypeHolder class="com.intellij.psi.impl.java.stubs.JavaStubElementTypes"/>