mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] implement constant pool parser and build id index using it for class files in jars IDEA-327594
GitOrigin-RevId: fff7c155b86e950e547fd8d01f142867c03c7c5c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5cca49b4f1
commit
5887fe8221
@@ -306,6 +306,8 @@
|
||||
<notificationGroup id="Infer Nullity" displayType="TOOL_WINDOW" toolWindowId="Infer Nullity" bundle="messages.JavaBundle" key="dialog.title.infer.nullity"/>
|
||||
<notificationGroup id="JDK Arch Check" displayType="STICKY_BALLOON" bundle="messages.JavaBundle" key="notification.group.arch.checker"/>
|
||||
|
||||
<registryKey key="index.ids.from.java.sources.in.jar" defaultValue="true" description="Index ids from class files instead of source files in JARs" restartRequired="false"/>
|
||||
|
||||
<registryKey key="ide.jps.use.build.tool.window" defaultValue="true" description="Enables 'Build' toolwindow for JPS compilation messages"/>
|
||||
<registryKey key="java.jps.backward.ref.index.builder.fs.case.sensitive" defaultValue="BY_ROOT"
|
||||
description="Case sensitivity of the project file system for compiler references. Available values: SENSITIVE, INSENSITIVE, BY_OS (Defined by OS or VM options), BY_ROOT (Defined by first root)"/>
|
||||
@@ -1007,6 +1009,7 @@
|
||||
<highlightVisitor implementation="com.intellij.codeInsight.daemon.JavaRainbowVisitor"/>
|
||||
<problemHighlightFilter implementation="com.intellij.codeInsight.daemon.JavaProblemHighlightFilter"/>
|
||||
<todoIndexer filetype="JAVA" implementationClass="com.intellij.psi.impl.cache.impl.idCache.JavaTodoIndexer"/>
|
||||
<idIndexer filetype="CLASS" implementationClass="com.intellij.psi.impl.cache.impl.idCache.JavaIdIndexer"/>
|
||||
<idIndexer filetype="JAVA" implementationClass="com.intellij.psi.impl.cache.impl.idCache.JavaIdIndexer"/>
|
||||
<fileIconPatcher id="javaFileIconPatcher" implementation="com.intellij.ide.JavaFileIconPatcher"/>
|
||||
<basicWordSelectionFilter implementation="com.intellij.codeInsight.editorActions.wordSelection.JavaBasicWordSelectionFilter"/>
|
||||
@@ -1267,6 +1270,7 @@
|
||||
<enterHandlerDelegate implementation="com.intellij.codeInsight.editorActions.JavaEnterInTextBlockHandler"/>
|
||||
<breadcrumbsInfoProvider implementation="com.intellij.lang.java.JavaBreadcrumbsInfoProvider"/>
|
||||
<editorFileSwapper implementation="com.intellij.codeEditor.JavaEditorFileSwapper"/>
|
||||
<binaryFileSourceProvider implementation="com.intellij.codeEditor.JavaBinaryFileSourceProvider" />
|
||||
<flipCommaIntention.flipper language="JAVA" implementationClass="com.intellij.codeInsight.intention.impl.JavaFlipper"/>
|
||||
<listSplitJoinContext language="JAVA" implementationClass="com.intellij.codeInsight.intention.impl.lists.JavaSplitJoinArgumentsContext"/>
|
||||
<listSplitJoinContext language="JAVA" implementationClass="com.intellij.codeInsight.intention.impl.lists.JavaSplitJoinParametersContext"/>
|
||||
@@ -2515,8 +2519,8 @@
|
||||
|
||||
<pluginSuggestionProvider implementation="com.intellij.ide.ant.AntSuggestionProvider"/>
|
||||
<pluginSuggestionProvider implementation="com.intellij.ide.android.AndroidSuggestionProvider"/>
|
||||
<statistics.counterUsagesCollector
|
||||
implementationClass="com.intellij.codeInsight.generation.analysis.GenerateLoggerStatisticsCollector"/>
|
||||
<statistics.counterUsagesCollector
|
||||
implementationClass="com.intellij.codeInsight.generation.analysis.GenerateLoggerStatisticsCollector"/>
|
||||
</extensions>
|
||||
|
||||
<extensions defaultExtensionNs="org.jetbrains">
|
||||
@@ -2550,6 +2554,8 @@
|
||||
<applicationListeners>
|
||||
<listener class="com.intellij.codeInsight.daemon.problems.pass.ProjectProblemFileFileEditorManagerListener"
|
||||
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"/>
|
||||
<listener class="com.intellij.psi.impl.cache.impl.idCache.JavaIdIndexRegistryValueListener"
|
||||
topic="com.intellij.openapi.util.registry.RegistryValueListener" />
|
||||
</applicationListeners>
|
||||
|
||||
<projectListeners>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeEditor;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.platform.indexing.BinaryFileSourceProvider;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class JavaBinaryFileSourceProvider implements BinaryFileSourceProvider {
|
||||
|
||||
@Override
|
||||
public @Nullable PsiFile findSourceFile(@NotNull PsiBinaryFile file) {
|
||||
VirtualFile virtualFile = JavaEditorFileSwapper.findSourceFile(file.getProject(), file.getVirtualFile());
|
||||
PsiManager manager = file.getManager();
|
||||
if (virtualFile == null) return null;
|
||||
return manager.findFile(virtualFile);
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.cache.impl.idCache;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ConstantPoolParser {
|
||||
private final @NotNull DataInput myInput;
|
||||
private final @NotNull Callback myCallback;
|
||||
|
||||
public ConstantPoolParser(@NotNull DataInput input, @NotNull Callback callback) {
|
||||
myInput = input;
|
||||
myCallback = callback;
|
||||
}
|
||||
|
||||
public void parse() throws IOException, ClassFormatException {
|
||||
int magic = myInput.readInt();
|
||||
if(magic != 0xCAFEBABE) {
|
||||
throw new ClassFormatException("Invalid class file: magic incorrect");
|
||||
}
|
||||
|
||||
// version
|
||||
myInput.readShort();
|
||||
myInput.readShort();
|
||||
|
||||
int constantPoolCount = myInput.readShort();
|
||||
|
||||
for(int i = 0; i < constantPoolCount - 1;) {
|
||||
i += readConstant();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of slots occupied in constant pool
|
||||
*/
|
||||
@SuppressWarnings("DuplicateBranchesInSwitch")
|
||||
private int readConstant() throws IOException, ClassFormatException {
|
||||
int tag = myInput.readByte();
|
||||
switch (tag) {
|
||||
case 1 -> {
|
||||
String utf8String = myInput.readUTF();
|
||||
myCallback.onUtf8(utf8String); // Utf8
|
||||
}
|
||||
case 3 -> // Integer
|
||||
myInput.skipBytes(4); // Integer value
|
||||
case 4 -> // Float
|
||||
myInput.skipBytes(4); // Float value
|
||||
case 5 -> {// Long
|
||||
myInput.skipBytes(8); // Long value
|
||||
return 2;
|
||||
}
|
||||
case 6 -> { // Double
|
||||
myInput.skipBytes(8); // Double value
|
||||
return 2;
|
||||
}
|
||||
case 7 -> // Class
|
||||
myInput.skipBytes(2); // Class index
|
||||
case 8 -> // String
|
||||
myInput.skipBytes(2); // String index
|
||||
case 9 -> { // Fieldref
|
||||
myInput.skipBytes(4); // Class index + NameAndType index
|
||||
}
|
||||
case 10 -> { // Methodref
|
||||
myInput.skipBytes(4); // Class index + NameAndType index
|
||||
}
|
||||
case 11 -> { // InterfaceMethodref
|
||||
myInput.skipBytes(4); // Class index + NameAndType index
|
||||
}
|
||||
case 12 -> { // NameAndType
|
||||
myInput.skipBytes(4); // Name index + Type index
|
||||
}
|
||||
case 15 -> { // MethodHandle
|
||||
myInput.skipBytes(3); // Reference kind + Reference index
|
||||
}
|
||||
case 16 -> // MethodType
|
||||
myInput.skipBytes(2); // Descriptor index
|
||||
case 17 -> { // Dynamic
|
||||
myInput.skipBytes(4); // Bootstrap method index + NameAndType index
|
||||
}
|
||||
case 18 -> { // InvokeDynamic
|
||||
myInput.skipBytes(4); // Bootstrap method index + NameAndType index
|
||||
}
|
||||
case 19 -> // Module
|
||||
myInput.skipBytes(2); // Name index
|
||||
case 20 -> // Package
|
||||
myInput.skipBytes(2); // Name index
|
||||
default -> throw new ClassFormatException("Invalid constant pool entry tag: " + tag);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void onUtf8(String str);
|
||||
}
|
||||
|
||||
public static class ClassFormatException extends Exception {
|
||||
ClassFormatException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Is java id \uD83D\uDC69\u200D: " + StringUtil.isJavaIdentifier("\uD83D\uDC69\u200D"));
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.cache.impl.idCache
|
||||
|
||||
import com.intellij.openapi.util.registry.RegistryValue
|
||||
import com.intellij.openapi.util.registry.RegistryValueListener
|
||||
import com.intellij.psi.impl.cache.impl.id.IdIndex
|
||||
import com.intellij.util.indexing.FileBasedIndex
|
||||
|
||||
class JavaIdIndexRegistryValueListener : RegistryValueListener {
|
||||
override fun afterValueChanged(value: RegistryValue) {
|
||||
if (JavaIdIndexer.ENABLED_REGISTRY_KEY == value.key) {
|
||||
JavaIdIndexer.isEnabled = value.asBoolean()
|
||||
FileBasedIndex.getInstance().requestRebuild(IdIndex.NAME)
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
-5
@@ -1,17 +1,89 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2019 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.psi.impl.cache.impl.idCache;
|
||||
|
||||
import com.intellij.ide.highlighter.JavaClassFileType;
|
||||
import com.intellij.lang.java.JavaParserDefinition;
|
||||
import com.intellij.lexer.Lexer;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.impl.cache.impl.OccurrenceConsumer;
|
||||
import com.intellij.psi.impl.cache.impl.id.LexerBasedIdIndexer;
|
||||
import com.intellij.psi.impl.cache.impl.id.IdDataConsumer;
|
||||
import com.intellij.psi.impl.cache.impl.id.IdIndexEntry;
|
||||
import com.intellij.psi.impl.cache.impl.id.IdIndexer;
|
||||
import com.intellij.psi.impl.source.JavaFileElementType;
|
||||
import com.intellij.psi.search.UsageSearchContext;
|
||||
import com.intellij.util.indexing.FileContent;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.psi.impl.cache.impl.BaseFilterLexerUtil.scanContentWithCheckCanceled;
|
||||
|
||||
public final class JavaIdIndexer implements IdIndexer {
|
||||
private final static Logger LOG = Logger.getInstance(JavaIdIndexer.class);
|
||||
static final String ENABLED_REGISTRY_KEY = "index.ids.from.java.sources.in.jar";
|
||||
static volatile boolean isEnabled = Registry.is("index.ids.from.java.sources.in.jar", true);
|
||||
|
||||
public final class JavaIdIndexer extends LexerBasedIdIndexer {
|
||||
@Override
|
||||
public @NotNull Lexer createLexer(final @NotNull OccurrenceConsumer consumer) {
|
||||
return createIndexingLexer(consumer);
|
||||
public @NotNull Map<IdIndexEntry, Integer> map(@NotNull FileContent inputData) {
|
||||
|
||||
VirtualFile file = inputData.getFile();
|
||||
String extension = file.getExtension();
|
||||
if (extension != null && extension.equals(JavaClassFileType.DEFAULT_EXTENSION) && isEnabled) {
|
||||
Map<IdIndexEntry, Integer> idEntries = calculateIdEntriesParsingConstantPool(inputData);
|
||||
if (idEntries != null) return idEntries;
|
||||
return Map.of();
|
||||
}
|
||||
// we are skipping indexing of sources in libraries (we are going to index only the compiled library classes)
|
||||
if (!isEnabled && !JavaFileElementType.isInSourceContent(file)) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
IdDataConsumer consumer = new IdDataConsumer();
|
||||
scanContentWithCheckCanceled(inputData, createIndexingLexer(new OccurrenceConsumer(consumer, false)));
|
||||
return consumer.getResult();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Map<IdIndexEntry, Integer> calculateIdEntriesParsingConstantPool(@NotNull FileContent inputData) {
|
||||
IdDataConsumer consumer = new IdDataConsumer();
|
||||
// optimised to avoid creation of list for every UTF8 entry
|
||||
IntList startInclList = new IntArrayList();
|
||||
IntList endExclList = new IntArrayList();
|
||||
ConstantPoolParser parser = new ConstantPoolParser(new DataInputStream(new ByteArrayInputStream(inputData.getContent())), str -> {
|
||||
ProgressManager.checkCanceled();
|
||||
if (str.isEmpty()) return;
|
||||
// it is likely a method signature, we don't need to handle it because all its parts will it is possible to find in the other entries.
|
||||
if (str.charAt(0) == '(') return;
|
||||
|
||||
if (JvmIdentifierUtil.collectJvmIdentifiers(str, (sequence, startIncl, endExcl) -> {
|
||||
startInclList.add(startIncl);
|
||||
endExclList.add(startIncl);
|
||||
})) {
|
||||
for (int i = 0; i < startInclList.size(); i++) {
|
||||
consumer.addOccurrence(str, startInclList.getInt(i), endExclList.getInt(i), UsageSearchContext.IN_CODE);
|
||||
}
|
||||
}
|
||||
startInclList.clear();
|
||||
endExclList.clear();
|
||||
});
|
||||
try {
|
||||
parser.parse();
|
||||
}
|
||||
catch (IOException | ConstantPoolParser.ClassFormatException e) {
|
||||
LOG.warn("Exception while handling file " + inputData.getFileName(), e);
|
||||
return null;
|
||||
}
|
||||
return consumer.getResult();
|
||||
}
|
||||
|
||||
public static Lexer createIndexingLexer(OccurrenceConsumer consumer) {
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.cache.impl.idCache;
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class JvmIdentifierUtil {
|
||||
private JvmIdentifierUtil() { }
|
||||
|
||||
public interface IdConsumer {
|
||||
/**
|
||||
* Handles valid JVM identifier extracted from the string
|
||||
* Warning: Implementation must be ready for rollback - several valid identifiers may be fed to {@code accept} before checking that there
|
||||
* is an invalid one present in this string and all previous ones should be rolled back.
|
||||
* @param sequence original sequence, from which the identifiers are extracted. Args correspond to offsets in the sequence.
|
||||
*/
|
||||
void accept(@NotNull CharSequence sequence, int startIncl, int endExcl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects JVM identifiers from a given string and passes them to the provided IdConsumer.
|
||||
*
|
||||
* @param str The string to collect JVM identifiers from.
|
||||
* @param partsConsumer The IdConsumer that will receive the collected JVM identifiers.
|
||||
* @return true if JVM identifiers are collected successfully, false otherwise.
|
||||
*/
|
||||
@Contract(mutates = "param2")
|
||||
public static boolean collectJvmIdentifiers(@NotNull String str, @NotNull IdConsumer partsConsumer) {
|
||||
if (str.isEmpty()) return false;
|
||||
|
||||
if (str.charAt(str.length() - 1) == ';' || str.charAt(0) == '(') {
|
||||
// probably field descriptor, e.g. [[Ljava/lang/String; or [B or S
|
||||
// also it may be Signature, e.g.
|
||||
// Ljava/util/Map<[Ljava/lang/String;Ljava/util/List<Ljava/util/List<+Ljava/util/Comparator<Ljava/lang/String;>;>;>;>;
|
||||
// It doesn't make sense to take the strings out of it recursively, so instead parts between 'L' and (';' or '<') are searched
|
||||
int current = 0;
|
||||
int start;
|
||||
outer:
|
||||
while (current < str.length()) {
|
||||
char firstChar = str.charAt(current);
|
||||
if (firstChar == 'L') {
|
||||
current++;
|
||||
start = current;
|
||||
while (current < str.length()) {
|
||||
char ch = str.charAt(current);
|
||||
current++;
|
||||
if (ch == ';' || ch == '<') {
|
||||
if (!collectSlashSeparated(str, start, current - 1, partsConsumer)) {
|
||||
return false;
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
current++;
|
||||
}
|
||||
if (current == str.length()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return collectSlashSeparated(str, 0, str.length(), partsConsumer);
|
||||
}
|
||||
|
||||
private static int indexOfSlash(String str, int startIndexIncl, int endIndexExcl) {
|
||||
int current = startIndexIncl;
|
||||
while (current < endIndexExcl) {
|
||||
char ch = str.charAt(current);
|
||||
if (ch == '/') {
|
||||
return current;
|
||||
}
|
||||
|
||||
current++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean collectSlashSeparated(@NotNull String str, int startIncl, int endExcl, @NotNull IdConsumer partsConsumer) {
|
||||
int partStartIncl = startIncl;
|
||||
while (partStartIncl < endExcl) {
|
||||
int indexOfSlash = indexOfSlash(str, partStartIncl, endExcl);
|
||||
if (indexOfSlash == -1) {
|
||||
break;
|
||||
}
|
||||
if (partStartIncl == indexOfSlash) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtil.isJavaIdentifier(str, partStartIncl, indexOfSlash)) {
|
||||
return false;
|
||||
}
|
||||
partsConsumer.accept(str, partStartIncl, indexOfSlash);
|
||||
partStartIncl = indexOfSlash + 1;
|
||||
if (partStartIncl == endExcl) { // '/' is at the end
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// handling rest
|
||||
if (partStartIncl == 0) {
|
||||
if (!StringUtil.isJavaIdentifier(str, 0, str.length())) {
|
||||
return false;
|
||||
}
|
||||
partsConsumer.accept(str, partStartIncl, endExcl);
|
||||
} else {
|
||||
if (!StringUtil.isJavaIdentifier(str, partStartIncl, endExcl)) {
|
||||
return false;
|
||||
}
|
||||
partsConsumer.accept(str, partStartIncl, endExcl);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,545 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl
|
||||
|
||||
import com.intellij.psi.impl.cache.impl.idCache.ConstantPoolParser
|
||||
import com.intellij.psi.impl.cache.impl.idCache.JvmIdentifierUtil.collectJvmIdentifiers
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase5
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.DataInputStream
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.readBytes
|
||||
|
||||
class ConstantPoolParserTest : LightJavaCodeInsightFixtureTestCase5() {
|
||||
private val constantPoolPath: Path by lazy { Paths.get(fixture.testDataPath).resolve("psi").resolve("constantPool") }
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class Simple {
|
||||
* String field = "my field";
|
||||
*
|
||||
* void method() {
|
||||
*
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Test fun testSimple() = doTest("""
|
||||
Code
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
Object
|
||||
Simple
|
||||
SourceFile
|
||||
String
|
||||
field
|
||||
java
|
||||
lang
|
||||
method
|
||||
this
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class WithParameter {
|
||||
* public static void foo(String[] s) {}
|
||||
* public static void bar(Map<String[], List<List<? extends Comparator<String>>>> s) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testWithParameter() = doTest("""
|
||||
Code
|
||||
Comparator
|
||||
LineNumberTable
|
||||
List
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
Map
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
String
|
||||
WithParameter
|
||||
bar
|
||||
example
|
||||
foo
|
||||
java
|
||||
lang
|
||||
org
|
||||
s
|
||||
this
|
||||
util
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* @RuntimeAnnotation.RuntimeAnn
|
||||
* public class RuntimeAnnotation {
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* @Target(ElementType.TYPE)
|
||||
* @interface RuntimeAnn {
|
||||
*
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@Test fun testRuntimeAnnotation() = doTest("""
|
||||
Code
|
||||
InnerClasses
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
Object
|
||||
RuntimeAnn
|
||||
RuntimeAnnotation
|
||||
RuntimeAnnotation${'$'}RuntimeAnn
|
||||
RuntimeVisibleAnnotations
|
||||
SourceFile
|
||||
java
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* @RuntimeAnnotationWithArgs.RuntimeAnn(justEnum = RuntimeAnnotationWithArgs.MyEnum.Variant2)
|
||||
* public class RuntimeAnnotationWithArgs {
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* @Target(ElementType.TYPE)
|
||||
* @interface RuntimeAnn {
|
||||
* MyEnum enumWithDefault() default MyEnum.Variant1;
|
||||
* MyEnum justEnum();
|
||||
* }
|
||||
*
|
||||
* enum MyEnum {
|
||||
* Variant1,
|
||||
* Variant2,
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testRuntimeAnnotationWithArgs() = doTest("""
|
||||
Code
|
||||
InnerClasses
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
MyEnum
|
||||
Object
|
||||
RuntimeAnn
|
||||
RuntimeAnnotationWithArgs
|
||||
RuntimeAnnotationWithArgs${'$'}MyEnum
|
||||
RuntimeAnnotationWithArgs${'$'}RuntimeAnn
|
||||
RuntimeVisibleAnnotations
|
||||
SourceFile
|
||||
Variant2
|
||||
java
|
||||
justEnum
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* package pack;
|
||||
*
|
||||
* public @interface AnnotationDeclaration {
|
||||
* String stringValue() default "my default value";
|
||||
* MyEnum enumValue();
|
||||
*
|
||||
* enum MyEnum {
|
||||
* Variant1,
|
||||
* Variant2,
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testAnnotationDeclaration() = doTest("""
|
||||
Annotation
|
||||
AnnotationDeclaration
|
||||
AnnotationDeclaration${'$'}MyEnum
|
||||
AnnotationDefault
|
||||
InnerClasses
|
||||
MyEnum
|
||||
Object
|
||||
SourceFile
|
||||
String
|
||||
annotation
|
||||
enumValue
|
||||
java
|
||||
lang
|
||||
pack
|
||||
stringValue
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
*public enum MyEnum {
|
||||
* Variant1,
|
||||
* Variant2,
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testMyEnum() = doTest("""
|
||||
${'$'}VALUES
|
||||
Class
|
||||
Code
|
||||
Enum
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
MyEnum
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
String
|
||||
Variant1
|
||||
Variant2
|
||||
clone
|
||||
java
|
||||
lang
|
||||
name
|
||||
this
|
||||
valueOf
|
||||
values
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class DoubleAndLongFields {
|
||||
* double d = 10.0;
|
||||
* long l = 4L;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testDoubleAndLongFields() = doTest("""
|
||||
Code
|
||||
D
|
||||
DoubleAndLongFields
|
||||
J
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
Object
|
||||
SourceFile
|
||||
d
|
||||
java
|
||||
l
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class TryCatch {
|
||||
* void foo() {
|
||||
* try {
|
||||
* bar();
|
||||
* } catch (RuntimeException e) {
|
||||
*
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* native void bar();
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Test fun testTryCatch() = doTest("""
|
||||
Code
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
Object
|
||||
RuntimeException
|
||||
SourceFile
|
||||
StackMapTable
|
||||
TryCatch
|
||||
bar
|
||||
foo
|
||||
java
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* import java.util.List;
|
||||
*
|
||||
* public class MethodWithExtendsWildcard {
|
||||
* public void id(List<? extends CharSequence> param) {}
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Test fun testMethodWithExtendsWildcard() {
|
||||
doTest("""
|
||||
CharSequence
|
||||
Code
|
||||
LineNumberTable
|
||||
List
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
MethodWithExtendsWildcard
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
id
|
||||
java
|
||||
lang
|
||||
param
|
||||
this
|
||||
util
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* import java.util.List;
|
||||
*
|
||||
* public class MethodWithSuperWildcard {
|
||||
* public void id(List<? super CharSequence> param) {}
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Test fun testMethodWithSuperWildcard() {
|
||||
doTest("""
|
||||
CharSequence
|
||||
Code
|
||||
LineNumberTable
|
||||
List
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
MethodWithSuperWildcard
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
id
|
||||
java
|
||||
lang
|
||||
param
|
||||
this
|
||||
util
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class MethodWithTypeParam {
|
||||
* public <TYPE_PARAM> void id(TYPE_PARAM param) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testMethodWithTypeParam() {
|
||||
doTest("""
|
||||
Code
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
MethodWithTypeParam
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
id
|
||||
java
|
||||
lang
|
||||
param
|
||||
this
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class MethodWithTypeParamExtends {
|
||||
* public <TYPE_PARAM extends CharSequence> void id(TYPE_PARAM param) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testMethodWithTypeParamExtends() {
|
||||
doTest("""
|
||||
CharSequence
|
||||
Code
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
MethodWithTypeParamExtends
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
id
|
||||
java
|
||||
lang
|
||||
param
|
||||
this
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* import java.util.List;
|
||||
*
|
||||
* public class MethodWithWildcard {
|
||||
* public void id(List<?> param) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testMethodWithWildcard() {
|
||||
doTest("""
|
||||
Code
|
||||
LineNumberTable
|
||||
List
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
MethodWithWildcard
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
id
|
||||
java
|
||||
lang
|
||||
param
|
||||
this
|
||||
util
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* public class TypeParameterInClass <TYPE_PARAM extends CharSequence> {}
|
||||
* ```
|
||||
*/
|
||||
@Test fun testTypeParameterInClass() {
|
||||
doTest("""
|
||||
CharSequence
|
||||
Code
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
Object
|
||||
Signature
|
||||
SourceFile
|
||||
TypeParameterInClass
|
||||
java
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* import java.util.List;
|
||||
*
|
||||
* public class SeveralBounds<T extends Comparable<String> & List<Integer>> {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testSeveralBounds() {
|
||||
doTest("""
|
||||
Code
|
||||
Comparable
|
||||
Integer
|
||||
LineNumberTable
|
||||
List
|
||||
LocalVariableTable
|
||||
LocalVariableTypeTable
|
||||
Object
|
||||
SeveralBounds
|
||||
Signature
|
||||
SourceFile
|
||||
String
|
||||
java
|
||||
lang
|
||||
this
|
||||
util
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* import java.util.List;
|
||||
*
|
||||
* public class SeveralBoundsInMethod {
|
||||
* <T extends CharSequence & Comparable<Integer>>void foo() {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Test fun testSeveralBoundsInMethod() {
|
||||
doTest("""
|
||||
Code
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
Object
|
||||
SeveralBoundsInMethod
|
||||
Signature
|
||||
SourceFile
|
||||
foo
|
||||
java
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Source:
|
||||
* ```
|
||||
* import java.util.List;
|
||||
*
|
||||
* public class Dollar$InClass {
|
||||
* void dollarIn$Method() {}
|
||||
* int $inField;
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Test fun `testDollar$InClass`() {
|
||||
doTest("""
|
||||
${'$'}inField
|
||||
Code
|
||||
Dollar${'$'}InClass
|
||||
I
|
||||
LineNumberTable
|
||||
LocalVariableTable
|
||||
Object
|
||||
SourceFile
|
||||
dollarIn${'$'}Method
|
||||
java
|
||||
lang
|
||||
this
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
private fun doTest(text: String) {
|
||||
val filePath = constantPoolPath.resolve(getTestName(false) + ".class")
|
||||
val bytes = filePath.readBytes()
|
||||
val ids = extractIdsFromClassFile(bytes)
|
||||
Assertions.assertEquals(text.trim(), ids.joinToString("\n").trim())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun extractIdsFromClassFile(bytes: ByteArray): List<String> {
|
||||
val parts = ArrayList<CharSequence>()
|
||||
ConstantPoolParser(DataInputStream(ByteArrayInputStream(bytes))) {
|
||||
val tmp = ArrayList<CharSequence>()
|
||||
if (collectJvmIdentifiers(it) { sequence: CharSequence, start: Int, end: Int -> tmp.add(sequence.subSequence(start, end)) }) {
|
||||
parts.addAll(tmp)
|
||||
}
|
||||
}.parse()
|
||||
val ids = parts.map { it.toString() }.toSet().sorted()
|
||||
return ids
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl;
|
||||
|
||||
import com.intellij.psi.impl.cache.impl.idCache.JvmIdentifierUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JvmIdentifierUtilTest {
|
||||
@Test
|
||||
void testIdentifierExtraction() {
|
||||
Assertions.assertEquals(List.of("foo"), getJvmIdParts("foo"));
|
||||
Assertions.assertEquals(List.of("foo", "bar"), getJvmIdParts("foo/bar"));
|
||||
Assertions.assertEquals(List.of("a", "bb", "ccc"), getJvmIdParts("a/bb/ccc"));
|
||||
Assertions.assertEquals(List.of("aaa", "bb", "c"), getJvmIdParts("aaa/bb/c"));
|
||||
Assertions.assertEquals(List.of("java", "lang", "String"), getJvmIdParts("[[Ljava/lang/String;"));
|
||||
Assertions.assertEquals(List.of("java", "lang", "Comparable", "java", "lang", "String", "java", "util", "List", "java", "lang", "Integer", "java", "lang", "Object"), getJvmIdParts("<T::Ljava/lang/Comparable<Ljava/lang/String;>;:Ljava/util/List<Ljava/lang/Integer;>;>Ljava/lang/Object;"));
|
||||
Assertions.assertEquals(List.of(
|
||||
"java", "util", "Map", "java", "lang", "String", "java", "util", "List", "java", "util", "List", "java", "util", "Comparator", "java", "lang", "String"
|
||||
), getJvmIdParts("Ljava/util/Map<[Ljava/lang/String;Ljava/util/List<Ljava/util/List<+Ljava/util/Comparator<Ljava/lang/String;>;>;>;>;"));
|
||||
Assertions.assertEquals(List.of("foo$bar"), getJvmIdParts("foo$bar"));
|
||||
Assertions.assertNull(getJvmIdParts("1"));
|
||||
Assertions.assertNull(getJvmIdParts("1/bar"));
|
||||
Assertions.assertNull(getJvmIdParts("/"));
|
||||
Assertions.assertNull(getJvmIdParts("with/spa ce"));
|
||||
Assertions.assertNull(getJvmIdParts("asd*"));
|
||||
Assertions.assertNull(getJvmIdParts("My sentence with spaces"));
|
||||
Assertions.assertNull(getJvmIdParts(""));
|
||||
Assertions.assertNull(getJvmIdParts("foo/"));
|
||||
Assertions.assertNull(getJvmIdParts("/foo"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
List<String> getJvmIdParts(String str) {
|
||||
ArrayList<CharSequence> parts = new ArrayList<>();
|
||||
if (JvmIdentifierUtil.collectJvmIdentifiers(str, (sequence1, start, end) -> parts.add(sequence1.subSequence(start, end)))) {
|
||||
return ContainerUtil.map(parts, sequence -> sequence.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -240,6 +240,9 @@ c:com.intellij.lang.findUsages.LanguageFindUsages
|
||||
- a:getElement():com.intellij.psi.PsiElement
|
||||
- a:getOffsetInElement():I
|
||||
- s:of(com.intellij.psi.PsiElement,I):com.intellij.model.search.TextOccurrence
|
||||
com.intellij.platform.indexing.BinaryFileSourceProvider
|
||||
- sf:EP:com.intellij.openapi.extensions.ExtensionPointName
|
||||
- a:findSourceFile(com.intellij.psi.PsiBinaryFile):com.intellij.psi.PsiFile
|
||||
com.intellij.psi.impl.cache.CacheManager
|
||||
- a:getFilesWithWord(java.lang.String,S,com.intellij.psi.search.GlobalSearchScope,Z):com.intellij.psi.PsiFile[]
|
||||
- s:getInstance(com.intellij.openapi.project.Project):com.intellij.psi.impl.cache.CacheManager
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
</extensionPoint>
|
||||
<extensionPoint name="findModelExtension" interface="com.intellij.find.FindModelExtension" dynamic="true"/>
|
||||
<extensionPoint name="codeUsageScopeOptimizer" interface="com.intellij.psi.search.ScopeOptimizer" dynamic="true"/>
|
||||
<extensionPoint name="binaryFileSourceProvider" interface="com.intellij.platform.indexing.BinaryFileSourceProvider" dynamic="true"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.indexing;
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.psi.PsiBinaryFile;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* In the situation when the identifiers are provided by the binary file (implemented {@link com.intellij.psi.impl.cache.impl.id.IdIndexer}
|
||||
* for binary file) which corresponds to some source file, this extension is used to find the original source file during the Find Usages.
|
||||
*/
|
||||
public interface BinaryFileSourceProvider {
|
||||
ExtensionPointName<BinaryFileSourceProvider> EP = new ExtensionPointName<>("com.intellij.binaryFileSourceProvider");
|
||||
|
||||
/**
|
||||
* Finds the original source file corresponding to the given binary file.
|
||||
*
|
||||
* @param file the binary file for which to find the source file
|
||||
* @return the original source file, or {@code null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
@RequiresReadLock
|
||||
PsiFile findSourceFile(@NotNull PsiBinaryFile file);
|
||||
}
|
||||
+3
-1
@@ -12,6 +12,7 @@ import com.intellij.psi.impl.cache.impl.todo.TodoIndexers;
|
||||
import com.intellij.psi.search.IndexPattern;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
import com.intellij.util.indexing.FileContent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -48,7 +49,8 @@ public final class BaseFilterLexerUtil {
|
||||
return todoMap != null ? todoMap : Collections.emptyMap();
|
||||
}
|
||||
|
||||
private static void scanContentWithCheckCanceled(@NotNull FileContent content, @NotNull Lexer filterLexer) {
|
||||
@ApiStatus.Internal
|
||||
public static void scanContentWithCheckCanceled(@NotNull FileContent content, @NotNull Lexer filterLexer) {
|
||||
filterLexer.start(content.getContentAsText());
|
||||
int tokenIdx = 0;
|
||||
while (filterLexer.getTokenType() != null) {
|
||||
|
||||
-1
@@ -104,7 +104,6 @@ public final class IdDataConsumer {
|
||||
myOccurrences.forEach((hash, value) -> consumer.accept(new IdIndexEntry(hash), value));
|
||||
}
|
||||
};
|
||||
|
||||
public @NotNull Map<IdIndexEntry, Integer> getResult() {
|
||||
return myResult;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.util.text.TrigramBuilder;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.platform.indexing.BinaryFileSourceProvider;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
import com.intellij.psi.impl.cache.CacheManager;
|
||||
@@ -370,7 +371,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
try {
|
||||
progress.setText(IndexingBundle.message("psi.scanning.files.progress"));
|
||||
|
||||
Processor<PsiElement> localProcessor = localProcessor(searcher, processor);
|
||||
Processor<CandidateFileInfo> localProcessor = localProcessor(searcher, processor);
|
||||
|
||||
// Lists of files to search in this order.
|
||||
// First, there are lists with higher probability of hits (e.g., files with `containerName` or files near the target)
|
||||
@@ -449,6 +450,9 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: candidateFile and file might be actually 2 different files (e.g. we may find class file in java, but the PsiFile
|
||||
* will be from mirror source class)
|
||||
*
|
||||
* @param files to scan for references in this pass.
|
||||
* @param totalSize the number of files to scan in both passes. Can be different from {@code files.size()} in case of
|
||||
* two-pass scan, where we first scan files containing container name and then all the rest files.
|
||||
@@ -459,7 +463,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
int totalSize,
|
||||
int alreadyProcessedFiles,
|
||||
@NotNull ProgressIndicator progress,
|
||||
@NotNull Processor<? super PsiFile> localProcessor) {
|
||||
@NotNull Processor<CandidateFileInfo> localProcessor) {
|
||||
return myManager.runInBatchFilesMode(() -> {
|
||||
AtomicInteger counter = new AtomicInteger(alreadyProcessedFiles);
|
||||
AtomicBoolean stopped = new AtomicBoolean(false);
|
||||
@@ -488,6 +492,15 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: candidateFile and file might be actually two different files (e.g. we may find class file in java, but the PsiFile
|
||||
* will be from mirror source class)
|
||||
*/
|
||||
record CandidateFileInfo(
|
||||
VirtualFile candidateFile,
|
||||
PsiFile file
|
||||
) { }
|
||||
|
||||
// Tries to run {@code localProcessor} for each file in {@code files} concurrently on ForkJoinPool.
|
||||
// When encounters write action request, stops all threads, waits for write action to finish and re-starts all threads again,
|
||||
// trying to finish the unprocessed files (i.e. those for which {@code localProcessor} hasn't been called yet).
|
||||
@@ -590,7 +603,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
|
||||
private void processVirtualFile(@NotNull VirtualFile vfile,
|
||||
@NotNull AtomicBoolean stopped,
|
||||
@NotNull Processor<? super PsiFile> localProcessor) throws ApplicationUtil.CannotRunReadActionException {
|
||||
@NotNull Processor<CandidateFileInfo> localProcessor) throws ApplicationUtil.CannotRunReadActionException {
|
||||
// try to pre-cache virtual file content outside read action to avoid stalling EDT
|
||||
if (!vfile.isDirectory() && !vfile.getFileType().isBinary()) {
|
||||
try {
|
||||
@@ -608,6 +621,13 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
file = ((BackFileViewProvider)file.getViewProvider()).getFrontPsiFile();
|
||||
}
|
||||
|
||||
if (file instanceof PsiBinaryFile binaryFile) {
|
||||
PsiFile originalFile = findOriginalFile(binaryFile);
|
||||
if (originalFile != null) {
|
||||
file = originalFile;
|
||||
}
|
||||
}
|
||||
|
||||
if (file != null && !(file instanceof PsiBinaryFile)) {
|
||||
Project project = myManager.getProject();
|
||||
if (project.isDisposed()) throw new ProcessCanceledException();
|
||||
@@ -627,7 +647,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!localProcessor.process(psiRoot)) {
|
||||
if (!localProcessor.process(new CandidateFileInfo(vfile, psiRoot))) {
|
||||
stopped.set(true);
|
||||
break;
|
||||
}
|
||||
@@ -638,6 +658,16 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiFile findOriginalFile(@NotNull PsiBinaryFile file) {
|
||||
List<BinaryFileSourceProvider> providers = BinaryFileSourceProvider.EP.getExtensionList();
|
||||
for (BinaryFileSourceProvider provider : providers) {
|
||||
PsiFile originalFile = provider.findSourceFile(file);
|
||||
if (originalFile != null) return originalFile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void getFilesWithText(@NotNull GlobalSearchScope scope,
|
||||
short searchContext,
|
||||
boolean caseSensitively,
|
||||
@@ -793,7 +823,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
Map<TextIndexQuery, Collection<RequestWithProcessor>> globals = new HashMap<>();
|
||||
List<Computable<Boolean>> customs = new ArrayList<>();
|
||||
Set<RequestWithProcessor> locals = new LinkedHashSet<>();
|
||||
Map<RequestWithProcessor, Processor<? super PsiElement>> localProcessors = new HashMap<>();
|
||||
Map<RequestWithProcessor, Processor<CandidateFileInfo>> localProcessors = new HashMap<>();
|
||||
distributePrimitives(collectors, locals, globals, customs, localProcessors);
|
||||
if (!processGlobalRequestsOptimized(globals, progress, localProcessors)) {
|
||||
return false;
|
||||
@@ -834,10 +864,11 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
CHANGED,
|
||||
}
|
||||
|
||||
static @NotNull Processor<PsiElement> localProcessor(@NotNull StringSearcher searcher, @NotNull BulkOccurrenceProcessor processor) {
|
||||
static @NotNull Processor<CandidateFileInfo> localProcessor(@NotNull StringSearcher searcher, @NotNull BulkOccurrenceProcessor processor) {
|
||||
return new ReadActionProcessor<>() {
|
||||
@Override
|
||||
public boolean processInReadAction(PsiElement scopeElement) {
|
||||
public boolean processInReadAction(CandidateFileInfo candidateFileInfo) {
|
||||
PsiElement scopeElement = candidateFileInfo.file();
|
||||
if (scopeElement instanceof PsiCompiledElement) {
|
||||
// can't scan text of the element
|
||||
return true;
|
||||
@@ -856,7 +887,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
|
||||
private boolean processGlobalRequestsOptimized(@NotNull Map<TextIndexQuery, Collection<RequestWithProcessor>> singles,
|
||||
@NotNull ProgressIndicator progress,
|
||||
@NotNull Map<RequestWithProcessor, Processor<? super PsiElement>> localProcessors) {
|
||||
@NotNull Map<RequestWithProcessor, Processor<CandidateFileInfo>> localProcessors) {
|
||||
if (singles.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -874,7 +905,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
|
||||
<T extends WordRequestInfo> boolean processGlobalRequests(@NotNull Map<TextIndexQuery, Collection<T>> singles,
|
||||
@NotNull ProgressIndicator progress,
|
||||
@NotNull Map<T, Processor<? super PsiElement>> localProcessors) {
|
||||
@NotNull Map<T, Processor<CandidateFileInfo>> localProcessors) {
|
||||
progress.pushState();
|
||||
progress.setText(IndexingBundle.message("psi.scanning.files.progress"));
|
||||
boolean result;
|
||||
@@ -926,22 +957,22 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
return result;
|
||||
}
|
||||
|
||||
private <T> boolean processCandidates(@NotNull Map<T, Processor<? super PsiElement>> localProcessors,
|
||||
private <T> boolean processCandidates(@NotNull Map<T, Processor<CandidateFileInfo>> localProcessors,
|
||||
@NotNull Map<VirtualFile, Collection<T>> candidateFiles,
|
||||
@NotNull ProgressIndicator progress,
|
||||
int totalSize,
|
||||
int alreadyProcessedFiles) {
|
||||
List<VirtualFile> files = new ArrayList<>(candidateFiles.keySet());
|
||||
|
||||
return processPsiFileRoots(files, totalSize, alreadyProcessedFiles, progress, psiRoot -> {
|
||||
VirtualFile vfile = psiRoot.getVirtualFile();
|
||||
return processPsiFileRoots(files, totalSize, alreadyProcessedFiles, progress, candidateInfo -> {
|
||||
VirtualFile vfile = candidateInfo.candidateFile();
|
||||
if (vfile instanceof BackedVirtualFile) {
|
||||
vfile = ((BackedVirtualFile)vfile).getOriginFile();
|
||||
}
|
||||
for (T singleRequest : candidateFiles.get(vfile)) {
|
||||
ProgressManager.checkCanceled();
|
||||
Processor<? super PsiElement> localProcessor = localProcessors.get(singleRequest);
|
||||
if (!localProcessor.process(psiRoot)) {
|
||||
Processor<CandidateFileInfo> localProcessor = localProcessors.get(singleRequest);
|
||||
if (!localProcessor.process(candidateInfo)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1090,7 +1121,7 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
@NotNull Set<RequestWithProcessor> locals,
|
||||
@NotNull Map<TextIndexQuery, Collection<RequestWithProcessor>> globals,
|
||||
@NotNull List<? super Computable<Boolean>> customs,
|
||||
@NotNull Map<RequestWithProcessor, Processor<? super PsiElement>> localProcessors) {
|
||||
@NotNull Map<RequestWithProcessor, Processor<CandidateFileInfo>> localProcessors) {
|
||||
for (Map.Entry<SearchRequestCollector, Processor<? super PsiReference>> entry : collectors.entrySet()) {
|
||||
ProgressManager.checkCanceled();
|
||||
Processor<? super PsiReference> processor = entry.getValue();
|
||||
@@ -1120,9 +1151,9 @@ public class PsiSearchHelperImpl implements PsiSearchHelper {
|
||||
StringSearcher searcher = new StringSearcher(primitive.word, primitive.caseSensitive, true, false);
|
||||
BulkOccurrenceProcessor adapted = adaptProcessor(primitive, singleRequest.refProcessor);
|
||||
|
||||
Processor<PsiElement> localProcessor = localProcessor(searcher, adapted);
|
||||
Processor<CandidateFileInfo> localProcessor = localProcessor(searcher, adapted);
|
||||
|
||||
Processor<? super PsiElement> old = localProcessors.put(singleRequest, localProcessor);
|
||||
Processor<CandidateFileInfo> old = localProcessors.put(singleRequest, localProcessor);
|
||||
assert old == null : old + ";" + localProcessor +"; singleRequest="+singleRequest;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ClassExtension
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.impl.search.PsiSearchHelperImpl.CandidateFileInfo
|
||||
import com.intellij.psi.search.LocalSearchScope
|
||||
import com.intellij.psi.search.PsiSearchHelper
|
||||
import com.intellij.psi.search.SearchSession
|
||||
@@ -225,8 +225,8 @@ private class Layer<T>(
|
||||
return myHelper.processGlobalRequests(globalsIds, progress, scopeProcessors(globals))
|
||||
}
|
||||
|
||||
private fun scopeProcessors(globals: Collection<RequestAndProcessors>): Map<WordRequestInfo, Processor<in PsiElement>> {
|
||||
val result = HashMap<WordRequestInfo, Processor<in PsiElement>>()
|
||||
private fun scopeProcessors(globals: Collection<RequestAndProcessors>): Map<WordRequestInfo, Processor<CandidateFileInfo>> {
|
||||
val result = HashMap<WordRequestInfo, Processor<CandidateFileInfo>>()
|
||||
for (requestAndProcessors: RequestAndProcessors in globals) {
|
||||
progress.checkCanceled()
|
||||
result[requestAndProcessors.request] = scopeProcessor(requestAndProcessors)
|
||||
@@ -234,7 +234,7 @@ private class Layer<T>(
|
||||
return result
|
||||
}
|
||||
|
||||
private fun scopeProcessor(requestAndProcessors: RequestAndProcessors): Processor<in PsiElement> {
|
||||
private fun scopeProcessor(requestAndProcessors: RequestAndProcessors): Processor<CandidateFileInfo> {
|
||||
val (request: WordRequestInfo, processors: RequestProcessors) = requestAndProcessors
|
||||
val searcher = StringSearcher(request.word, request.isCaseSensitive, true, false)
|
||||
val adapted = MyBulkOccurrenceProcessor(project, processors)
|
||||
|
||||
@@ -1973,6 +1973,7 @@ c:com.intellij.openapi.util.text.StringUtil
|
||||
- s:isEmptyOrSpaces(java.lang.String):Z
|
||||
- s:isEscapedBackslash(java.lang.CharSequence,I,I):Z
|
||||
- s:isHexDigit(C):Z
|
||||
- s:isJavaIdentifier(java.lang.CharSequence,I,I):Z
|
||||
- s:isJavaIdentifier(java.lang.String):Z
|
||||
- s:isJavaIdentifierPart(C):Z
|
||||
- s:isJavaIdentifierStart(C):Z
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.intellij.util.*;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import com.intellij.util.text.CharSequenceSubSequence;
|
||||
import com.intellij.util.text.MergingCharSequence;
|
||||
import kotlin.jvm.internal.Ref;
|
||||
import org.jetbrains.annotations.*;
|
||||
import org.jetbrains.annotations.ApiStatus.NonExtendable;
|
||||
|
||||
@@ -2275,6 +2276,9 @@ public class StringUtil {
|
||||
return cp >= '0' && cp <= '9' || cp >= 'a' && cp <= 'z' || cp >= 'A' && cp <= 'Z' || Character.isJavaIdentifierPart(cp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff the string is a valid java identifier (according to JLS 3.8)
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
public static boolean isJavaIdentifier(@NotNull String text) {
|
||||
int len = text.length();
|
||||
@@ -2291,6 +2295,58 @@ public class StringUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the char sequence slice is a valid java identifier (according to JLS 3.8)
|
||||
* @return true exactly iff {@code isJavaIdentifier(str.subSequence(startIncl, endExcl)) == true }
|
||||
*/
|
||||
public static boolean isJavaIdentifier(@NotNull CharSequence str, int startIncl, int endExcl) {
|
||||
if (str.length() == 0) return false;
|
||||
Ref.BooleanRef isFirst = new Ref.BooleanRef();
|
||||
isFirst.element = true;
|
||||
return processCodePoints(str, startIncl, endExcl, codePoint -> {
|
||||
if (isFirst.element) {
|
||||
isFirst.element = false;
|
||||
if (!isJavaIdentifierStart(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!isJavaIdentifierPart(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private interface CodePointProcessor {
|
||||
/**
|
||||
* @return true iff should continue
|
||||
*/
|
||||
boolean acceptCodePoint(int codePoint);
|
||||
}
|
||||
|
||||
|
||||
private static boolean processCodePoints(@NotNull CharSequence str, int startIncl, int endExcl, CodePointProcessor processor) {
|
||||
int i = startIncl;
|
||||
while (i < endExcl) {
|
||||
char c1 = str.charAt(i++);
|
||||
if (!Character.isHighSurrogate(c1) || i >= endExcl) {
|
||||
if (!processor.acceptCodePoint(c1)) return false;
|
||||
}
|
||||
else {
|
||||
char c2 = str.charAt(i);
|
||||
if (Character.isLowSurrogate(c2)) {
|
||||
i++;
|
||||
if (!processor.acceptCodePoint(Character.toCodePoint(c1, c2))) return false;
|
||||
}
|
||||
else {
|
||||
if (!processor.acceptCodePoint(c2)) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape property name or key in property file. Unicode characters are escaped as well.
|
||||
*
|
||||
|
||||
@@ -943,6 +943,23 @@ public class StringUtilTest {
|
||||
assertTrue(StringUtil.isJavaIdentifier("\u03B1A"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
@Test
|
||||
public void testCharSequenceSliceIsJavaIdentifier() {
|
||||
assertFalse(StringUtil.isJavaIdentifier("", 0, 0));
|
||||
assertTrue(StringUtil.isJavaIdentifier("x", 0, 1));
|
||||
assertFalse(StringUtil.isJavaIdentifier("0", 0, 1));
|
||||
assertFalse(StringUtil.isJavaIdentifier("0x", 0, 2));
|
||||
assertTrue(StringUtil.isJavaIdentifier("foo$bar", 0, 7));
|
||||
assertTrue(StringUtil.isJavaIdentifier("x0", 0, 2));
|
||||
assertTrue(StringUtil.isJavaIdentifier("\uD835\uDEFCA", 0, 3));
|
||||
assertTrue(StringUtil.isJavaIdentifier("A\uD835\uDEFC", 0, 3));
|
||||
assertTrue(StringUtil.isJavaIdentifier("\u03B1A", 0, 2));
|
||||
assertTrue(StringUtil.isJavaIdentifier("###\u03B1A", 3, 5));
|
||||
assertTrue(StringUtil.isJavaIdentifier("\u03B1A###", 0, 2));
|
||||
assertTrue(StringUtil.isJavaIdentifier("###\u03B1A###", 3, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplit() {
|
||||
String spaceSeparator = " ";
|
||||
|
||||
Reference in New Issue
Block a user