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;
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);
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();
}
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) {
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;
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;
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(" ");
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) {
}
}
+ 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();
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();
}
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;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import java.util.*;
+import java.util.stream.Collectors;
/**
* @author dmitry lomov
@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();
}
}
\ No newline at end of file
--- /dev/null
+/*
+ * 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);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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,}");
+ }
+}
\ No newline at end of file
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;
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));
}
}
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)
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;
--- /dev/null
+/*
+ * 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))
+}
\ No newline at end of file
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!/")
}
}
<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"/>