SSR: better search plan for faster and more correct index search

This commit is contained in:
Bas Leijdekkers
2018-11-07 21:07:57 +01:00
parent 89c426c062
commit 6dac93eff3
5 changed files with 89 additions and 112 deletions
@@ -31,6 +31,8 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor.OccurenceKind.*;
/**
* @author Eugene.Kudelevsky
*/
@@ -64,7 +66,7 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
final String word = reference.getReferenceName();
if (!handleWord(word, myCompilingVisitor.getContext())) return;
if (!handleWord(word, CODE, myCompilingVisitor.getContext())) return;
if (reference.isQualified() && isClassFromJavaLangPackage(reference.resolve())) return;
super.visitReferenceElement(reference);
}
@@ -83,38 +85,64 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
@Override
public void visitMethod(PsiMethod method) {
if (!handleWord(method.getName(), myCompilingVisitor.getContext())) return;
if (!handleWord(method.getName(), CODE, myCompilingVisitor.getContext())) return;
super.visitMethod(method);
}
@Override
public void visitVariable(PsiVariable variable) {
if (!handleWord(variable.getName(), myCompilingVisitor.getContext())) return;
if (!handleWord(variable.getName(), CODE, myCompilingVisitor.getContext())) return;
super.visitVariable(variable);
}
@Override
public void visitCatchSection(PsiCatchSection section) {
final PsiParameter parameter = section.getParameter();
if (parameter != null && !handleWord(parameter.getName(), CODE, myCompilingVisitor.getContext())) return;
super.visitCatchSection(section);
}
@Override
public void visitClass(PsiClass aClass) {
if (!handleWord(aClass.getName(), myCompilingVisitor.getContext())) return;
final CompileContext context = myCompilingVisitor.getContext();
if (!handleWord(aClass.getName(), CODE, context)) return;
if (aClass.isInterface()) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.INTERFACE, true, CODE, context);
}
else if (aClass.isEnum()) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.ENUM, true, CODE, context);
}
else {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.INTERFACE, false, CODE, context);
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.ENUM, false, CODE, context);
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.CLASS, true, CODE, context);
}
super.visitClass(aClass);
}
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
final PsiType type = expression.getType();
if (PsiType.BOOLEAN.equals(type) || PsiType.NULL.equals(type)) {
// don't search index for literals of other types, as they can be written in many many kinds of ways for the same value.
if (!handleWord(expression.getText(), CODE, myCompilingVisitor.getContext())) return;
}
super.visitLiteralExpression(expression);
}
@Override
public void visitElement(PsiElement element) {
super.visitElement(element);
if (element instanceof PsiMethodReferenceExpression) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord("::", true, GlobalCompilingVisitor.OccurenceKind.CODE,
myCompilingVisitor.getContext());
GlobalCompilingVisitor.addFilesToSearchForGivenWord("::", true, CODE, myCompilingVisitor.getContext());
}
else if (element instanceof PsiLambdaExpression) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord("->", true, GlobalCompilingVisitor.OccurenceKind.CODE,
myCompilingVisitor.getContext());
GlobalCompilingVisitor.addFilesToSearchForGivenWord("->", true, CODE, myCompilingVisitor.getContext());
}
else if (element instanceof PsiKeyword) {
final String keyword = element.getText();
if (!excludedKeywords.contains(keyword)) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(keyword, true, GlobalCompilingVisitor.OccurenceKind.CODE,
myCompilingVisitor.getContext());
GlobalCompilingVisitor.addFilesToSearchForGivenWord(keyword, true, CODE, myCompilingVisitor.getContext());
}
}
}
@@ -224,7 +252,7 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
if (StringUtil.isQuotedString(text)) {
@Nullable MatchingHandler handler =
myCompilingVisitor.processPatternStringWithFragments(text, GlobalCompilingVisitor.OccurenceKind.LITERAL);
myCompilingVisitor.processPatternStringWithFragments(text, LITERAL);
if (PsiType.CHAR.equals(expression.getType()) &&
(handler instanceof LiteralWithSubstitutionHandler || handler == null && expression.getValue() == null)) {
@@ -464,20 +492,6 @@ public class JavaCompilingVisitor extends JavaRecursiveElementWalkingVisitor {
}
GlobalCompilingVisitor.setFilter(handler, ClassFilter.getInstance());
if (!(handler instanceof SubstitutionHandler) || ((SubstitutionHandler)handler).getMinOccurs() > 0) {
if (psiClass.isInterface()) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.INTERFACE, true, GlobalCompilingVisitor.OccurenceKind.CODE, context);
}
else if (psiClass.isEnum()) {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.ENUM, true, GlobalCompilingVisitor.OccurenceKind.CODE, context);
}
else {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.INTERFACE, false, GlobalCompilingVisitor.OccurenceKind.CODE, context);
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.ENUM, false, GlobalCompilingVisitor.OccurenceKind.CODE, context);
GlobalCompilingVisitor.addFilesToSearchForGivenWord(PsiKeyword.CLASS, true, GlobalCompilingVisitor.OccurenceKind.CODE, context);
}
}
}
private void createAndSetSubstitutionHandlerFromReference(final PsiElement expr, final String referenceText, boolean classQualifier) {
@@ -2,6 +2,7 @@
package com.intellij.structuralsearch.impl.matcher.compiler;
import com.intellij.dupLocator.util.NodeFilter;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.structuralsearch.MalformedPatternException;
import com.intellij.structuralsearch.StructuralSearchProfile;
@@ -31,6 +32,7 @@ public class GlobalCompilingVisitor {
@NonNls private static final String SUBSTITUTION_PATTERN_STR = "\\b(__\\$_\\w+)\\b";
private static final Pattern ourSubstitutionPattern = Pattern.compile(SUBSTITUTION_PATTERN_STR);
private static final Set<String> ourReservedWords = new HashSet<>(Arrays.asList(MODIFIER_ANNOTATION_NAME, INSTANCE_MODIFIER_NAME));
private static final NodeFilter ourFilter = LexicalNodesFilter.getInstance();
static {
for (StructuralSearchProfile profile : StructuralSearchProfile.EP_NAME.getExtensionList()) {
@@ -38,16 +40,10 @@ public class GlobalCompilingVisitor {
}
}
private static final Pattern ourAlternativePattern = Pattern.compile("^\\((.+)\\)$");
@NonNls private static final String WORD_SEARCH_PATTERN_STR = ".*?\\b(.+?)\\b.*?";
static final Pattern ourWordSearchPattern = Pattern.compile(WORD_SEARCH_PATTERN_STR);
private CompileContext context;
private final List<PsiElement> myLexicalNodes = new SmartList<>();
private int myCodeBlockLevel;
private static final NodeFilter ourFilter = LexicalNodesFilter.getInstance();
public static NodeFilter getFilter() {
return ourFilter;
}
@@ -243,68 +239,12 @@ public class GlobalCompilingVisitor {
}
public void processTokenizedName(String name, boolean skipComments, GlobalCompilingVisitor.OccurenceKind kind) {
WordTokenizer tokenizer = new WordTokenizer(name);
for (Iterator<String> i = tokenizer.iterator(); i.hasNext();) {
String nextToken = i.next();
if (skipComments &&
(nextToken.equals("/*") || nextToken.equals("/**") || nextToken.equals("*/") || nextToken.equals("*") || nextToken.equals("//"))
) {
continue;
}
Matcher matcher = ourAlternativePattern.matcher(nextToken);
if (matcher.matches()) {
StringTokenizer alternatives = new StringTokenizer(matcher.group(1), "|");
while (alternatives.hasMoreTokens()) {
addFilesToSearchForGivenWord(alternatives.nextToken(), !alternatives.hasMoreTokens(), kind, getContext());
}
}
else {
addFilesToSearchForGivenWord(nextToken, true, kind, getContext());
}
for (String word : StringUtil.getWordsInStringLongestFirst(name)) {
addFilesToSearchForGivenWord(word, true, kind, getContext());
}
}
public enum OccurenceKind {
LITERAL, COMMENT, CODE, TEXT
}
private static class WordTokenizer {
private final List<String> myWords = new SmartList<>();
WordTokenizer(String text) {
final StringTokenizer tokenizer = new StringTokenizer(text);
Matcher matcher = null;
while (tokenizer.hasMoreTokens()) {
String nextToken = tokenizer.nextToken();
if (matcher == null) {
matcher = ourWordSearchPattern.matcher(nextToken);
}
else {
matcher.reset(nextToken);
}
nextToken = (matcher.matches()) ? matcher.group(1) : nextToken;
int lastWordStart = 0;
int i;
for (i = 0; i < nextToken.length(); ++i) {
if (!Character.isJavaIdentifierStart(nextToken.charAt(i))) {
if (i != lastWordStart) {
myWords.add(nextToken.substring(lastWordStart, i));
}
lastWordStart = i + 1;
}
}
if (i != lastWordStart) {
myWords.add(nextToken.substring(lastWordStart, i));
}
}
}
Iterator<String> iterator() {
return myWords.iterator();
}
}
}
@@ -11,8 +11,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import static com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor.OccurenceKind.CODE;
/**
* @author Bas Leijdekkers
*/
@@ -20,9 +18,10 @@ public interface WordOptimizer {
/**
* @param text text to check index with
* @param kind LITERAL, COMMENT, CODE or TEXT
* @return true, if psi tree should be processed deeper, false otherwise.
*/
default boolean handleWord(@Nullable String text, CompileContext compileContext) {
default boolean handleWord(@Nullable String text, GlobalCompilingVisitor.OccurenceKind kind, CompileContext compileContext) {
final OptimizingSearchHelper searchHelper = compileContext.getSearchHelper();
if (!searchHelper.doOptimizing()) {
return false;
@@ -35,7 +34,7 @@ public interface WordOptimizer {
if (pattern.isTypedVar(word)) {
final SubstitutionHandler handler = (SubstitutionHandler)pattern.getHandler(word);
if (handler == null || handler.getMinOccurs() == 0) {
// don't call super
// don't call super visit so psi tree is not processed deeper
return false;
}
@@ -49,12 +48,12 @@ public interface WordOptimizer {
searchHelper.endTransaction();
}
else {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(predicate.getRegExp(), true, CODE, compileContext);
GlobalCompilingVisitor.addFilesToSearchForGivenWord(predicate.getRegExp(), true, kind, compileContext);
}
}
}
else {
GlobalCompilingVisitor.addFilesToSearchForGivenWord(word, true, CODE, compileContext);
GlobalCompilingVisitor.addFilesToSearchForGivenWord(word, true, kind, compileContext);
}
}
return true;
@@ -9,6 +9,7 @@ import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
import com.intellij.structuralsearch.impl.matcher.filters.TagValueFilter;
import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
import static com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor.OccurenceKind.CODE;
import static com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor.OccurenceKind.TEXT;
/**
@@ -35,23 +36,21 @@ public class XmlCompilingVisitor extends XmlRecursiveElementVisitor {
@Override
public void visitXmlTag(XmlTag tag) {
if (!handleWord(tag.getName(), myCompilingVisitor.getContext())) return;
if (!handleWord(tag.getName(), CODE, myCompilingVisitor.getContext())) return;
super.visitXmlTag(tag);
}
@Override
public void visitXmlAttribute(XmlAttribute attribute) {
if (!handleWord(attribute.getName(), myCompilingVisitor.getContext())) return;
handleWord(attribute.getValue(), myCompilingVisitor.getContext());
if (!handleWord(attribute.getName(), CODE, myCompilingVisitor.getContext())) return;
handleWord(attribute.getValue(), CODE, myCompilingVisitor.getContext());
super.visitXmlAttribute(attribute);
}
@Override
public void visitXmlText(XmlText text) {
final String string = text.getText();
if (!myCompilingVisitor.getContext().getPattern().isTypedVar(string)) {
myCompilingVisitor.processTokenizedName(string, false, TEXT);
}
handleWord(string, TEXT, myCompilingVisitor.getContext());
super.visitXmlText(text);
}
}
@@ -37,7 +37,7 @@ public class OptimizedSearchScanTest extends StructuralSearchTestCase {
"class C {" +
" void '_m{0,1} () throws OMGWTFBBQException {}" +
"}");
assertEquals("exception should not be in plan", "[in code:class|in code:enum|in code:interface][in code:C]", plan);
assertEquals("exception should not be in plan", "[in code:C][in code:class|in code:enum|in code:interface]", plan);
final String plan2 = findWordsToBeUsedWhenSearchingFor(
"class C {" +
@@ -47,17 +47,17 @@ public class OptimizedSearchScanTest extends StructuralSearchTestCase {
" }" +
"}");
assertEquals("throws should not be in plan",
"[in code:class|in code:enum|in code:interface][in code:C][in code:m][in code:String][in code:println][in code:out]" +
"[in code:System][in code:return]",
"[in code:C][in code:class|in code:enum|in code:interface][in code:m][in code:String][in code:println][in code:out]" +
"[in code:System][in code:return][in code:null]",
plan2);
}
public void testExtendsImplements() {
final String plan1 = findWordsToBeUsedWhenSearchingFor("class A extends '_B{0,0} {}");
assertEquals("extends should not be in plan", "[in code:class|in code:enum|in code:interface][in code:A]", plan1);
assertEquals("extends should not be in plan", "[in code:A][in code:class|in code:enum|in code:interface]", plan1);
final String plan2 = findWordsToBeUsedWhenSearchingFor("class B implements '_I{0,0} {}");
assertEquals("implements should not be in plan", "[in code:class|in code:enum|in code:interface][in code:B]", plan2);
assertEquals("implements should not be in plan", "[in code:B][in code:class|in code:enum|in code:interface]", plan2);
}
public void testLambda() {
@@ -81,23 +81,24 @@ public class OptimizedSearchScanTest extends StructuralSearchTestCase {
public void testClasses() {
final String plan1 = findWordsToBeUsedWhenSearchingFor("class A {}");
assertEquals("[in code:class|in code:enum|in code:interface][in code:A]", plan1);
assertEquals("[in code:A][in code:class|in code:enum|in code:interface]", plan1);
final String plan2 = findWordsToBeUsedWhenSearchingFor("interface I {}");
assertEquals("[in code:interface][in code:I]", plan2);
assertEquals("[in code:I][in code:interface]", plan2);
final String plan3 = findWordsToBeUsedWhenSearchingFor("enum E {}");
assertEquals("[in code:enum][in code:E]", plan3);
assertEquals("[in code:E][in code:enum]", plan3);
}
public void testDescendants() {
final String plan = findWordsToBeUsedWhenSearchingFor("class '_A:*List {}");
assertEquals("classes outside search scope should alse be added to descendants plan",
"[in code:class|in code:enum|in code:interface][in code:AbstractList|in code:AbstractSequentialList|in code:ArrayList|" +
"[in code:AbstractList|in code:AbstractSequentialList|in code:ArrayList|" +
"in code:CheckedList|in code:CheckedRandomAccessList|in code:CopiesList|in code:EmptyList|in code:List|" +
"in code:SingletonList|in code:SubList|in code:SynchronizedList|in code:SynchronizedRandomAccessList|" +
"in code:UnmodifiableList|in code:UnmodifiableRandomAccessList]", plan);
"in code:UnmodifiableList|in code:UnmodifiableRandomAccessList]" +
"[in code:class|in code:enum|in code:interface]", plan);
final String plan2 = findWordsToBeUsedWhenSearchingFor("enum '_E:*Zyxwvuts {}");
assertEquals("non-existing class name should be added to plan", "[in code:enum][in code:Zyxwvuts]", plan2);
assertEquals("non-existing class name should be added to plan", "[in code:Zyxwvuts][in code:enum]", plan2);
}
public void testQualifiedReference() {
@@ -107,4 +108,28 @@ public class OptimizedSearchScanTest extends StructuralSearchTestCase {
final String plan2 = findWordsToBeUsedWhenSearchingFor("new java.lang.reflect.InvocationTargetException('_x)");
assertEquals("[in code:new][in code:InvocationTargetException][in code:reflect][in code:lang][in code:java]", plan2);
}
public void testTryWithoutCatch() {
final String plan = findWordsToBeUsedWhenSearchingFor("try {" +
" '_st*;" +
"} catch ('_Type '_exception{0,0}) {" +
" '_st2*;" +
"}");
assertEquals("[in code:try]", plan);
}
public void testComment() {
final String plan = findWordsToBeUsedWhenSearchingFor("/* one/two (3|4|5) */");
assertEquals("[in comments:one][in comments:two][in comments:3][in comments:4][in comments:5]", plan);
}
public void testPackageLocal() {
final String plan = findWordsToBeUsedWhenSearchingFor("@Modifier(\"packageLocal\") '_FieldType '_Field = '_Init?;");
assertEquals("", plan);
}
public void testLiterals() {
final String plan = findWordsToBeUsedWhenSearchingFor("assert '_exp != null && true: \"'_exp is null\";");
assertEquals("[in literals:null][in literals:is][in code:assert][in code:null][in code:true]", plan);
}
}