SSR: merge Matcher and MatcherImpl

This commit is contained in:
Bas Leijdekkers
2017-06-04 12:32:11 +02:00
parent 01cf093e1f
commit d4f7b685fa
6 changed files with 579 additions and 665 deletions
@@ -15,25 +15,195 @@
*/
package com.intellij.structuralsearch;
import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
import com.intellij.dupLocator.iterators.NodeIterator;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.structuralsearch.impl.matcher.*;
import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
import com.intellij.structuralsearch.plugin.ui.Configuration;
import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink;
import com.intellij.structuralsearch.plugin.util.DuplicateFilteringResultSink;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PairProcessor;
import com.intellij.util.SmartList;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* This class makes program structure tree matching:
*/
public class Matcher extends MatcherImpl {
public class Matcher {
private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl");
// project being worked on
private final Project project;
private final DumbService myDumbService;
// context of matching
private final MatchContext matchContext;
private boolean isTesting;
// visitor to delegate the real work
private final GlobalMatchingVisitor visitor = new GlobalMatchingVisitor();
private ProgressIndicator progress;
private final TaskScheduler scheduler = new TaskScheduler();
private int totalFilesToScan;
private int scannedFilesCount;
public Matcher(Project project) {
super(project);
this(project, null);
}
public Matcher(final Project project, final MatchOptions matchOptions) {
super(project, matchOptions);
this.project = project;
matchContext = new MatchContext();
matchContext.setMatcher(visitor);
if (matchOptions != null) {
matchContext.setOptions(matchOptions);
cacheCompiledPattern(matchOptions, PatternCompiler.compilePattern(project, matchOptions));
}
myDumbService = DumbService.getInstance(project);
}
static class LastMatchData {
CompiledPattern lastPattern;
MatchOptions lastOptions;
}
private static SoftReference<LastMatchData> lastMatchData;
private static final Object lastMatchDataLock = new Object();
public static void validate(Project project, MatchOptions options) {
synchronized (lastMatchDataLock) {
final LastMatchData data = new LastMatchData();
data.lastPattern = PatternCompiler.compilePattern(project, options);
data.lastOptions = options;
lastMatchData = new SoftReference<>(data);
}
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
assert profile != null;
profile.checkSearchPattern(project, options);
}
public static boolean checkIfShouldAttemptToMatch(MatchContext context, NodeIterator matchedNodes) {
final CompiledPattern pattern = context.getPattern();
final NodeIterator patternNodes = pattern.getNodes();
try {
while (true) {
final PsiElement patternNode = patternNodes.current();
if (patternNode == null) {
return true;
}
final PsiElement matchedNode = matchedNodes.current();
if (matchedNode == null) {
return false;
}
final MatchingHandler matchingHandler = pattern.getHandler(patternNode);
if (matchingHandler == null || !matchingHandler.canMatch(patternNode, matchedNode, context)) {
return false;
}
matchedNodes.advance();
patternNodes.advance();
}
} finally {
patternNodes.reset();
matchedNodes.reset();
}
}
public void processMatchesInElement(MatchContext context, Configuration configuration,
NodeIterator matchedNodes,
PairProcessor<MatchResult, Configuration> processor) {
try {
configureOptions(context, configuration, matchedNodes.current(), processor);
context.setShouldRecursivelyMatch(false);
visitor.matchContext(matchedNodes);
} finally {
matchedNodes.reset();
context.getOptions().setScope(null);
}
}
public void clearContext() {
matchContext.clear();
}
private void configureOptions(MatchContext context,
final Configuration configuration,
PsiElement psiFile,
final PairProcessor<MatchResult, Configuration> processor) {
if (psiFile == null) return;
LocalSearchScope scope = new LocalSearchScope(psiFile);
matchContext.clear();
matchContext.setMatcher(visitor);
MatchOptions options = context.getOptions();
matchContext.setOptions(options);
matchContext.setPattern(context.getPattern());
matchContext.setShouldRecursivelyMatch(context.shouldRecursivelyMatch());
visitor.setMatchContext(matchContext);
matchContext.setSink(
new DuplicateFilteringResultSink(
new DefaultMatchResultSink() {
@Override
public void newMatch(MatchResult result) {
processor.process(result, configuration);
}
}
)
);
options.setScope(scope);
}
public void precompileOptions(List<Configuration> configurations, Map<Configuration, MatchContext> out) {
for (final Configuration configuration : configurations) {
if (out.containsKey(configuration)) {
continue;
}
final MatchContext matchContext = new MatchContext();
matchContext.setMatcher(visitor);
final MatchOptions matchOptions = configuration.getMatchOptions();
matchContext.setOptions(matchOptions);
ReadAction.run(() -> {
try {
final CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions);
matchContext.setPattern(compiledPattern);
out.put(configuration, matchContext);
}
catch (UnsupportedPatternException | MalformedPatternException ignored) {}
});
}
}
/**
@@ -42,7 +212,119 @@ public class Matcher extends MatcherImpl {
* @throws UnsupportedPatternException
*/
public void findMatches(MatchResultSink sink, MatchOptions options) throws MalformedPatternException, UnsupportedPatternException {
super.findMatches(sink,options);
CompiledPattern compiledPattern = prepareMatching(sink, options);
if (compiledPattern== null) {
return;
}
matchContext.getSink().setMatchingProcess( scheduler );
scheduler.init();
progress = matchContext.getSink().getProgressIndicator();
if (isTesting) {
// testing mode;
final PsiElement[] elements = ((LocalSearchScope)options.getScope()).getScope();
PsiElement parent = elements[0].getParent();
if (elements.length > 0 && matchContext.getPattern().getStrategy().continueMatching(parent != null ? parent : elements[0])) {
visitor.matchContext(new SsrFilteringNodeIterator(new ArrayBackedNodeIterator(elements)));
}
else {
final LanguageFileType fileType = (LanguageFileType)matchContext.getOptions().getFileType();
final Language language = fileType.getLanguage();
for (PsiElement element : elements) {
match(element, language);
}
}
matchContext.getSink().matchingFinished();
return;
}
if (!findMatches(options, compiledPattern)) {
return;
}
if (scheduler.getTaskQueueEndAction()==null) {
scheduler.setTaskQueueEndAction(
() -> matchContext.getSink().matchingFinished()
);
}
scheduler.executeNext();
}
private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) {
SearchScope searchScope = compiledPattern.getScope();
final boolean ourOptimizedScope = searchScope != null;
if (!ourOptimizedScope) searchScope = options.getScope();
if (searchScope instanceof GlobalSearchScope) {
final GlobalSearchScope scope = (GlobalSearchScope)searchScope;
final ContentIterator ci = fileOrDir -> {
if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) {
++totalFilesToScan;
scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir));
}
return true;
};
ReadAction.run(() -> FileBasedIndex.getInstance().iterateIndexableFiles(ci, project, progress));
progress.setText2("");
}
else {
final PsiElement[] elementsToScan = ((LocalSearchScope)searchScope).getScope();
totalFilesToScan = elementsToScan.length;
for (int i = 0; i < elementsToScan.length; ++i) {
final PsiElement psiElement = elementsToScan[i];
if (psiElement == null) continue;
scheduler.addOneTask(new MatchOnePsiFile(psiElement));
if (ourOptimizedScope) elementsToScan[i] = null; // to prevent long PsiElement reference
}
}
return true;
}
private CompiledPattern prepareMatching(final MatchResultSink sink, final MatchOptions options) {
CompiledPattern savedPattern = null;
if (matchContext.getOptions() == options && matchContext.getPattern() != null &&
matchContext.getOptions().hashCode() == matchContext.getPattern().getOptionsHashStamp()) {
savedPattern = matchContext.getPattern();
}
matchContext.clear();
matchContext.setSink(new DuplicateFilteringResultSink(sink));
matchContext.setOptions(options);
matchContext.setMatcher(visitor);
visitor.setMatchContext(matchContext);
CompiledPattern compiledPattern = savedPattern;
if (compiledPattern == null) {
synchronized (lastMatchDataLock) {
final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData);
if (data != null && options == data.lastOptions) {
compiledPattern = data.lastPattern;
}
lastMatchData = null;
}
if (compiledPattern==null) {
compiledPattern = ReadAction.compute(() -> PatternCompiler.compilePattern(project, options));
}
}
cacheCompiledPattern(options, compiledPattern);
return compiledPattern;
}
private void cacheCompiledPattern(final MatchOptions options, final CompiledPattern compiledPattern) {
matchContext.setPattern(compiledPattern);
compiledPattern.setOptionsHashStamp(options.hashCode());
}
/**
@@ -53,41 +335,314 @@ public class Matcher extends MatcherImpl {
* @throws UnsupportedPatternException
*/
public List<MatchResult> testFindMatches(String source,
MatchOptions options,
boolean filePattern,
FileType sourceFileType,
String sourceExtension,
boolean physicalSourceFile)
MatchOptions options,
boolean fileContext,
FileType sourceFileType,
String sourceExtension,
boolean physicalSourceFile)
throws MalformedPatternException, UnsupportedPatternException {
return super.testFindMatches(source, options, filePattern, sourceFileType, sourceExtension, physicalSourceFile);
CollectingMatchResultSink sink = new CollectingMatchResultSink();
try {
PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source,
fileContext ? PatternTreeContext.File : PatternTreeContext.Block,
sourceFileType,
sourceExtension,
project, physicalSourceFile);
options.setScope(new LocalSearchScope(elements));
testFindMatches(sink, options);
}
catch (IncorrectOperationException e) {
MalformedPatternException exception = new MalformedPatternException();
exception.initCause(e);
throw exception;
} finally {
options.setScope(null);
}
return sink.getMatches();
}
public List<MatchResult> testFindMatches(String source, MatchOptions options, boolean filePattern)
public List<MatchResult> testFindMatches(String source, MatchOptions options, boolean fileContext)
throws MalformedPatternException, UnsupportedPatternException {
return super.testFindMatches(source, options, filePattern);
return testFindMatches(source, options, fileContext, options.getFileType(), null, false);
}
/**
* Finds the matches of given pattern starting from given tree element.
* @param sink
* @param options
* @param sink match result destination
* @throws MalformedPatternException
* @throws UnsupportedPatternException
*/
public void testFindMatches(MatchResultSink sink, MatchOptions options)
throws MalformedPatternException, UnsupportedPatternException {
super.testFindMatches(sink, options);
isTesting = true;
try {
findMatches(sink, options);
} finally {
isTesting = false;
}
}
class TaskScheduler implements MatchingProcess {
private ArrayList<Runnable> tasks = new ArrayList<>();
private boolean ended;
private Runnable taskQueueEndAction;
private boolean suspended;
@Override
public void stop() {
ended = true;
}
@Override
public void pause() {
suspended = true;
}
@Override
public void resume() {
if (!suspended) return;
suspended = false;
executeNext();
}
@Override
public boolean isSuspended() {
return suspended;
}
@Override
public boolean isEnded() {
return ended;
}
void setTaskQueueEndAction(Runnable taskQueueEndAction) {
this.taskQueueEndAction = taskQueueEndAction;
}
Runnable getTaskQueueEndAction () {
return taskQueueEndAction;
}
void addOneTask(Runnable runnable) {
tasks.add(runnable);
}
private void executeNext() {
while(!suspended && !ended) {
if (tasks.isEmpty()) {
ended = true;
break;
}
final Runnable task = tasks.remove(tasks.size() - 1);
try {
task.run();
}
catch (ProcessCanceledException | StructuralSearchException e) {
ended = true;
clearSchedule();
throw e;
}
catch (Throwable th) {
LOG.error(th);
}
}
if (ended) clearSchedule();
}
private void init() {
ended = false;
suspended = false;
PsiManager.getInstance(project).startBatchFilesProcessingMode();
}
private void clearSchedule() {
if (tasks != null) {
taskQueueEndAction.run();
if (!project.isDisposed()) {
PsiManager.getInstance(project).finishBatchFilesProcessingMode();
}
tasks = null;
}
}
}
/**
* Tests if given element is matched by given pattern starting from target variable. If matching succeeds
* then not null match result is returned.
* Initiates the matching process for given element
* @param element the current search tree element
*/
private void match(PsiElement element, final Language language) {
final MatchingStrategy strategy = matchContext.getPattern().getStrategy();
final Language elementLanguage = element.getLanguage();
if (strategy.continueMatching(element) && elementLanguage.isKindOf(language)) {
visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element}));
return;
}
for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) {
match(el, language);
}
if (element instanceof PsiLanguageInjectionHost) {
InjectedLanguageUtil.enumerate(element, (injectedPsi, places) -> match(injectedPsi, language));
}
}
/**
* Tests if given element is matched by given pattern starting from target variable.
* @throws MalformedPatternException
* @throws UnsupportedPatternException
*/
@NotNull
public List<MatchResult> matchByDownUp(PsiElement element, MatchOptions options)
throws MalformedPatternException, UnsupportedPatternException {
return super.matchByDownUp(element, options);
final CollectingMatchResultSink sink = new CollectingMatchResultSink();
final CompiledPattern compiledPattern = prepareMatching(sink, options);
matchContext.setShouldRecursivelyMatch(false);
PsiElement targetNode = compiledPattern.getTargetNode();
PsiElement elementToStartMatching = null;
if (targetNode == null) {
targetNode = compiledPattern.getNodes().current();
if (targetNode != null) {
compiledPattern.getNodes().advance();
assert !compiledPattern.getNodes().hasNext();
compiledPattern.getNodes().rewind();
element = element.getParent();
if (element == null) {
return Collections.emptyList();
}
while (element.getClass() != targetNode.getClass()) {
element = element.getParent();
if (element == null) return Collections.emptyList();
}
elementToStartMatching = element;
}
} else {
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
if (profile == null) return Collections.emptyList();
targetNode = profile.extendMatchedByDownUp(targetNode);
MatchingHandler handler = null;
while (element.getClass() == targetNode.getClass() ||
compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element, matchContext)) {
handler = compiledPattern.getHandler(targetNode);
handler.setPinnedElement(element);
elementToStartMatching = element;
if (handler instanceof TopLevelMatchingHandler) break;
element = element.getParent();
targetNode = targetNode.getParent();
if (options.isLooseMatching()) {
element = profile.updateCurrentNode(element);
targetNode = profile.updateCurrentNode(targetNode);
}
}
if (!(handler instanceof TopLevelMatchingHandler)) return Collections.emptyList();
}
assert targetNode != null : "Could not match down up when no target node";
final LanguageFileType fileType = (LanguageFileType)matchContext.getOptions().getFileType();
match(elementToStartMatching, fileType.getLanguage());
matchContext.getSink().matchingFinished();
return sink.getMatches();
}
private class MatchOnePsiFile extends MatchOneFile {
private PsiElement file;
MatchOnePsiFile(PsiElement file) {
this.file = file;
}
@NotNull
@Override
protected List<PsiElement> getPsiElementsToProcess() {
final PsiElement file = this.file;
this.file = null;
return new SmartList<>(file);
}
}
private class MatchOneVirtualFile extends MatchOneFile {
private final VirtualFile myFile;
public MatchOneVirtualFile(VirtualFile file) {
myFile = file;
}
@NotNull
@Override
protected List<PsiElement> getPsiElementsToProcess() {
return ReadAction.compute(
() -> {
if (!myFile.isValid()) {
// file may be been deleted since search started
return Collections.emptyList();
}
final PsiFile file = PsiManager.getInstance(project).findFile(myFile);
if (file == null) {
return Collections.emptyList();
}
final FileViewProvider viewProvider = file.getViewProvider();
final List<PsiElement> elementsToProcess = new SmartList<>();
for (Language lang : viewProvider.getLanguages()) {
elementsToProcess.add(viewProvider.getPsi(lang));
}
return elementsToProcess;
}
);
}
}
private abstract class MatchOneFile implements Runnable {
@Override
public void run() {
final List<PsiElement> files = getPsiElementsToProcess();
if (progress!=null) {
progress.setFraction((double)scannedFilesCount/totalFilesToScan);
}
++scannedFilesCount;
if (files.size() == 0) return;
final LanguageFileType fileType = (LanguageFileType)matchContext.getOptions().getFileType();
final Language patternLanguage = fileType.getLanguage();
for (final PsiElement file : files) {
if (file instanceof PsiFile) {
matchContext.getSink().processFile((PsiFile)file);
}
myDumbService.runReadActionInSmartMode(
() -> {
if (!file.isValid()) return;
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(file.getLanguage());
if (profile == null) {
return;
}
match(profile.extendMatchOnePsiFile(file), patternLanguage);
}
);
}
}
@NotNull
protected abstract List<PsiElement> getPsiElementsToProcess();
}
}
@@ -113,7 +113,7 @@ public class MatchContext {
this.sink = sink;
}
void clear() {
public void clear() {
result = null;
pattern = null;
}
@@ -1,638 +0,0 @@
/*
* Copyright 2000-2017 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.structuralsearch.impl.matcher;
import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
import com.intellij.dupLocator.iterators.NodeIterator;
import com.intellij.lang.Language;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.structuralsearch.*;
import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
import com.intellij.structuralsearch.plugin.ui.Configuration;
import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink;
import com.intellij.structuralsearch.plugin.util.DuplicateFilteringResultSink;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PairProcessor;
import com.intellij.util.SmartList;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* This class makes program structure tree matching:
*/
public class MatcherImpl {
private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl");
// project being worked on
private final Project project;
private final DumbService myDumbService;
// context of matching
private final MatchContext matchContext;
private boolean isTesting;
// visitor to delegate the real work
private final GlobalMatchingVisitor visitor = new GlobalMatchingVisitor();
private ProgressIndicator progress;
private final TaskScheduler scheduler = new TaskScheduler();
private int totalFilesToScan;
private int scannedFilesCount;
public MatcherImpl(final Project project, final MatchOptions matchOptions) {
this.project = project;
matchContext = new MatchContext();
matchContext.setMatcher(visitor);
if (matchOptions != null) {
matchContext.setOptions(matchOptions);
cacheCompiledPattern(matchOptions, PatternCompiler.compilePattern(project,matchOptions));
}
myDumbService = DumbService.getInstance(project);
}
static class LastMatchData {
CompiledPattern lastPattern;
MatchOptions lastOptions;
}
private static SoftReference<LastMatchData> lastMatchData;
private static final Object lastMatchDataLock = new Object();
protected MatcherImpl(Project project) {
this(project, null);
}
public static void validate(Project project, MatchOptions options) {
synchronized (lastMatchDataLock) {
final LastMatchData data = new LastMatchData();
data.lastPattern = PatternCompiler.compilePattern(project, options);
data.lastOptions = options;
lastMatchData = new SoftReference<>(data);
}
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
assert profile != null;
profile.checkSearchPattern(project, options);
}
public static boolean checkIfShouldAttemptToMatch(MatchContext context, NodeIterator matchedNodes) {
final CompiledPattern pattern = context.getPattern();
final NodeIterator patternNodes = pattern.getNodes();
try {
while (true) {
final PsiElement patternNode = patternNodes.current();
if (patternNode == null) {
return true;
}
final PsiElement matchedNode = matchedNodes.current();
if (matchedNode == null) {
return false;
}
final MatchingHandler matchingHandler = pattern.getHandler(patternNode);
if (matchingHandler == null || !matchingHandler.canMatch(patternNode, matchedNode, context)) {
return false;
}
matchedNodes.advance();
patternNodes.advance();
}
} finally {
patternNodes.reset();
matchedNodes.reset();
}
}
public void processMatchesInElement(MatchContext context, Configuration configuration,
NodeIterator matchedNodes,
PairProcessor<MatchResult, Configuration> processor) {
try {
configureOptions(context, configuration, matchedNodes.current(), processor);
context.setShouldRecursivelyMatch(false);
visitor.matchContext(matchedNodes);
} finally {
matchedNodes.reset();
context.getOptions().setScope(null);
}
}
public void clearContext() {
matchContext.clear();
}
private void configureOptions(MatchContext context,
final Configuration configuration,
PsiElement psiFile,
final PairProcessor<MatchResult, Configuration> processor) {
if (psiFile == null) return;
LocalSearchScope scope = new LocalSearchScope(psiFile);
matchContext.clear();
matchContext.setMatcher(visitor);
MatchOptions options = context.getOptions();
matchContext.setOptions(options);
matchContext.setPattern(context.getPattern());
matchContext.setShouldRecursivelyMatch(context.shouldRecursivelyMatch());
visitor.setMatchContext(matchContext);
matchContext.setSink(
new DuplicateFilteringResultSink(
new DefaultMatchResultSink() {
@Override
public void newMatch(MatchResult result) {
processor.process(result, configuration);
}
}
)
);
options.setScope(scope);
}
public void precompileOptions(List<Configuration> configurations, final Map<Configuration, MatchContext> out) {
for (final Configuration configuration : configurations) {
if (out.containsKey(configuration)) {
continue;
}
final MatchContext matchContext = new MatchContext();
matchContext.setMatcher(visitor);
final MatchOptions matchOptions = configuration.getMatchOptions();
matchContext.setOptions(matchOptions);
ReadAction.run(() -> {
try {
final CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions);
matchContext.setPattern(compiledPattern);
out.put(configuration, matchContext);
}
catch (UnsupportedPatternException | MalformedPatternException ignored) {}
});
}
}
/**
* Finds the matches of given pattern starting from given tree element.
* @throws MalformedPatternException
* @throws UnsupportedPatternException
*/
protected void findMatches(MatchResultSink sink, final MatchOptions options) throws MalformedPatternException, UnsupportedPatternException
{
CompiledPattern compiledPattern = prepareMatching(sink, options);
if (compiledPattern== null) {
return;
}
matchContext.getSink().setMatchingProcess( scheduler );
scheduler.init();
progress = matchContext.getSink().getProgressIndicator();
if (isTesting) {
// testing mode;
final PsiElement[] elements = ((LocalSearchScope)options.getScope()).getScope();
PsiElement parent = elements[0].getParent();
if (elements.length > 0 && matchContext.getPattern().getStrategy().continueMatching(parent != null ? parent : elements[0])) {
visitor.matchContext(new SsrFilteringNodeIterator(new ArrayBackedNodeIterator(elements)));
}
else {
final LanguageFileType fileType = (LanguageFileType)matchContext.getOptions().getFileType();
final Language language = fileType.getLanguage();
for (PsiElement element : elements) {
match(element, language);
}
}
matchContext.getSink().matchingFinished();
return;
}
if (!findMatches(options, compiledPattern)) {
return;
}
if (scheduler.getTaskQueueEndAction()==null) {
scheduler.setTaskQueueEndAction(
() -> matchContext.getSink().matchingFinished()
);
}
scheduler.executeNext();
}
private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) {
SearchScope searchScope = compiledPattern.getScope();
final boolean ourOptimizedScope = searchScope != null;
if (!ourOptimizedScope) searchScope = options.getScope();
if (searchScope instanceof GlobalSearchScope) {
final GlobalSearchScope scope = (GlobalSearchScope)searchScope;
final ContentIterator ci = fileOrDir -> {
if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) {
++totalFilesToScan;
scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir));
}
return true;
};
ReadAction.run(() -> FileBasedIndex.getInstance().iterateIndexableFiles(ci, project, progress));
progress.setText2("");
}
else {
final PsiElement[] elementsToScan = ((LocalSearchScope)searchScope).getScope();
totalFilesToScan = elementsToScan.length;
for (int i = 0; i < elementsToScan.length; ++i) {
final PsiElement psiElement = elementsToScan[i];
if (psiElement == null) continue;
scheduler.addOneTask(new MatchOnePsiFile(psiElement));
if (ourOptimizedScope) elementsToScan[i] = null; // to prevent long PsiElement reference
}
}
return true;
}
private CompiledPattern prepareMatching(final MatchResultSink sink, final MatchOptions options) {
CompiledPattern savedPattern = null;
if (matchContext.getOptions() == options && matchContext.getPattern() != null &&
matchContext.getOptions().hashCode() == matchContext.getPattern().getOptionsHashStamp()) {
savedPattern = matchContext.getPattern();
}
matchContext.clear();
matchContext.setSink(new DuplicateFilteringResultSink(sink));
matchContext.setOptions(options);
matchContext.setMatcher(visitor);
visitor.setMatchContext(matchContext);
CompiledPattern compiledPattern = savedPattern;
if (compiledPattern == null) {
synchronized (lastMatchDataLock) {
final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData);
if (data != null && options == data.lastOptions) {
compiledPattern = data.lastPattern;
}
lastMatchData = null;
}
if (compiledPattern==null) {
compiledPattern = ReadAction.compute(() -> PatternCompiler.compilePattern(project, options));
}
}
cacheCompiledPattern(options, compiledPattern);
return compiledPattern;
}
private void cacheCompiledPattern(final MatchOptions options, final CompiledPattern compiledPattern) {
matchContext.setPattern(compiledPattern);
compiledPattern.setOptionsHashStamp(options.hashCode());
}
/**
* Finds the matches of given pattern starting from given tree element.
* @param sink match result destination
* @throws MalformedPatternException
* @throws UnsupportedPatternException
*/
protected void testFindMatches(MatchResultSink sink, MatchOptions options)
throws MalformedPatternException, UnsupportedPatternException {
isTesting = true;
try {
findMatches(sink,options);
} finally {
isTesting = false;
}
}
/**
* Finds the matches of given pattern starting from given tree element.
* @param source string for search
* @return list of matches found
* @throws MalformedPatternException
* @throws UnsupportedPatternException
*/
protected List<MatchResult> testFindMatches(String source,
MatchOptions options,
boolean fileContext,
FileType sourceFileType,
String sourceExtension,
boolean physicalSourceFile)
throws MalformedPatternException, UnsupportedPatternException {
CollectingMatchResultSink sink = new CollectingMatchResultSink();
try {
PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source,
fileContext ? PatternTreeContext.File : PatternTreeContext.Block,
sourceFileType,
sourceExtension,
project, physicalSourceFile);
options.setScope(new LocalSearchScope(elements));
testFindMatches(sink, options);
}
catch (IncorrectOperationException e) {
MalformedPatternException exception = new MalformedPatternException();
exception.initCause(e);
throw exception;
} finally {
options.setScope(null);
}
return sink.getMatches();
}
protected List<MatchResult> testFindMatches(String source, MatchOptions options, boolean filePattern) {
return testFindMatches(source, options, filePattern, options.getFileType(), null, false);
}
class TaskScheduler implements MatchingProcess {
private ArrayList<Runnable> tasks = new ArrayList<>();
private boolean ended;
private Runnable taskQueueEndAction;
private boolean suspended;
@Override
public void stop() {
ended = true;
}
@Override
public void pause() {
suspended = true;
}
@Override
public void resume() {
if (!suspended) return;
suspended = false;
executeNext();
}
@Override
public boolean isSuspended() {
return suspended;
}
@Override
public boolean isEnded() {
return ended;
}
void setTaskQueueEndAction(Runnable taskQueueEndAction) {
this.taskQueueEndAction = taskQueueEndAction;
}
Runnable getTaskQueueEndAction () {
return taskQueueEndAction;
}
void addOneTask(Runnable runnable) {
tasks.add(runnable);
}
private void executeNext() {
while(!suspended && !ended) {
if (tasks.isEmpty()) {
ended = true;
break;
}
final Runnable task = tasks.remove(tasks.size() - 1);
try {
task.run();
}
catch (ProcessCanceledException | StructuralSearchException e) {
ended = true;
clearSchedule();
throw e;
}
catch (Throwable th) {
LOG.error(th);
}
}
if (ended) clearSchedule();
}
private void init() {
ended = false;
suspended = false;
PsiManager.getInstance(project).startBatchFilesProcessingMode();
}
private void clearSchedule() {
if (tasks != null) {
taskQueueEndAction.run();
if (!project.isDisposed()) {
PsiManager.getInstance(project).finishBatchFilesProcessingMode();
}
tasks = null;
}
}
}
private class MatchOnePsiFile extends MatchOneFile {
private PsiElement file;
MatchOnePsiFile(PsiElement file) {
this.file = file;
}
@NotNull
@Override
protected List<PsiElement> getPsiElementsToProcess() {
final PsiElement file = this.file;
this.file = null;
return new SmartList<>(file);
}
}
private abstract class MatchOneFile implements Runnable {
@Override
public void run() {
final List<PsiElement> files = getPsiElementsToProcess();
if (progress!=null) {
progress.setFraction((double)scannedFilesCount/totalFilesToScan);
}
++scannedFilesCount;
if (files.size() == 0) return;
final LanguageFileType fileType = (LanguageFileType)matchContext.getOptions().getFileType();
final Language patternLanguage = fileType.getLanguage();
for (final PsiElement file : files) {
if (file instanceof PsiFile) {
matchContext.getSink().processFile((PsiFile)file);
}
myDumbService.runReadActionInSmartMode(() -> {
if (!file.isValid()) return;
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(file.getLanguage());
if (profile == null) {
return;
}
match(profile.extendMatchOnePsiFile(file), patternLanguage);
}
);
}
}
@NotNull
protected abstract List<PsiElement> getPsiElementsToProcess();
}
// Initiates the matching process for given element
// @param element the current search tree element
void match(PsiElement element, final Language language) {
final MatchingStrategy strategy = matchContext.getPattern().getStrategy();
final Language elementLanguage = element.getLanguage();
if (strategy.continueMatching(element) && elementLanguage.isKindOf(language)) {
visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element}));
return;
}
for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) {
match(el, language);
}
if (element instanceof PsiLanguageInjectionHost) {
InjectedLanguageUtil.enumerate(element, (injectedPsi, places) -> match(injectedPsi, language));
}
}
@NotNull
protected List<MatchResult> matchByDownUp(PsiElement element, final MatchOptions options) {
final CollectingMatchResultSink sink = new CollectingMatchResultSink();
final CompiledPattern compiledPattern = prepareMatching(sink, options);
matchContext.setShouldRecursivelyMatch(false);
PsiElement targetNode = compiledPattern.getTargetNode();
PsiElement elementToStartMatching = null;
if (targetNode == null) {
targetNode = compiledPattern.getNodes().current();
if (targetNode != null) {
compiledPattern.getNodes().advance();
assert !compiledPattern.getNodes().hasNext();
compiledPattern.getNodes().rewind();
element = element.getParent();
if (element == null) {
return Collections.emptyList();
}
while (element.getClass() != targetNode.getClass()) {
element = element.getParent();
if (element == null) return Collections.emptyList();
}
elementToStartMatching = element;
}
} else {
final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
if (profile == null) return Collections.emptyList();
targetNode = profile.extendMatchedByDownUp(targetNode);
MatchingHandler handler = null;
while (element.getClass() == targetNode.getClass() ||
compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element, matchContext)) {
handler = compiledPattern.getHandler(targetNode);
handler.setPinnedElement(element);
elementToStartMatching = element;
if (handler instanceof TopLevelMatchingHandler) break;
element = element.getParent();
targetNode = targetNode.getParent();
if (options.isLooseMatching()) {
element = profile.updateCurrentNode(element);
targetNode = profile.updateCurrentNode(targetNode);
}
}
if (!(handler instanceof TopLevelMatchingHandler)) return Collections.emptyList();
}
assert targetNode != null : "Could not match down up when no target node";
final LanguageFileType fileType = (LanguageFileType)matchContext.getOptions().getFileType();
match(elementToStartMatching, fileType.getLanguage());
matchContext.getSink().matchingFinished();
return sink.getMatches();
}
private class MatchOneVirtualFile extends MatchOneFile {
private final VirtualFile myFile;
public MatchOneVirtualFile(VirtualFile file) {
myFile = file;
}
@NotNull
@Override
protected List<PsiElement> getPsiElementsToProcess() {
return ReadAction.compute(() -> {
if (!myFile.isValid()) {
// file may be been deleted since search started
return Collections.emptyList();
}
final PsiFile file = PsiManager.getInstance(project).findFile(myFile);
if (file == null) {
return Collections.emptyList();
}
final FileViewProvider viewProvider = file.getViewProvider();
final List<PsiElement> elementsToProcess = new SmartList<>();
for (Language lang : viewProvider.getLanguages()) {
elementsToProcess.add(viewProvider.getPsi(lang));
}
return elementsToProcess;
}
);
}
}
}
@@ -30,7 +30,6 @@ import com.intellij.structuralsearch.Matcher;
import com.intellij.structuralsearch.SSRBundle;
import com.intellij.structuralsearch.StructuralSearchException;
import com.intellij.structuralsearch.impl.matcher.MatchContext;
import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
import com.intellij.structuralsearch.plugin.replace.ReplacementInfo;
@@ -118,7 +117,7 @@ public class SSBasedInspection extends LocalInspectionTool {
Configuration configuration = entry.getKey();
MatchContext context = entry.getValue();
if (MatcherImpl.checkIfShouldAttemptToMatch(context, matchedNodes)) {
if (Matcher.checkIfShouldAttemptToMatch(context, matchedNodes)) {
final int nodeCount = context.getPattern().getNodeCount();
try {
matcher.processMatchesInElement(context, configuration, new CountingNodeIterator(nodeCount, matchedNodes), processor);
@@ -52,7 +52,6 @@ import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.structuralsearch.*;
import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
import com.intellij.structuralsearch.plugin.StructuralSearchPlugin;
import com.intellij.ui.ComboboxSpeedSearch;
import com.intellij.ui.IdeBorderFactory;
@@ -806,7 +805,7 @@ public class SearchDialog extends DialogWrapper {
boolean result = true;
try {
MatcherImpl.validate(searchContext.getProject(), model.getConfig().getMatchOptions());
Matcher.validate(searchContext.getProject(), model.getConfig().getMatchOptions());
}
catch (MalformedPatternException ex) {
if (myRunFindActionOnClose) {
@@ -23,7 +23,6 @@ import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.structuralsearch.impl.matcher.MatcherImpl;
import java.io.File;
import java.io.IOException;
@@ -67,7 +66,7 @@ abstract class StructuralSearchTestCase extends LightQuickFixTestCase {
options.setFileType(patternFileType);
options.setDialect(patternLanguage);
MatcherImpl.validate(getProject(), options);
Matcher.validate(getProject(), options);
return testMatcher.testFindMatches(in, options, true, sourceFileType, sourceExtension, physicalSourceFile);
}