diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java index ba8c03846aee..a72e770429a0 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/Matcher.java @@ -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; + + 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 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 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 configurations, Map 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 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 testFindMatches(String source, MatchOptions options, boolean filePattern) + public List 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 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 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 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 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 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 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 getPsiElementsToProcess(); } } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java index da5b937eb974..8b3788d7bd9c 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java @@ -113,7 +113,7 @@ public class MatchContext { this.sink = sink; } - void clear() { + public void clear() { result = null; pattern = null; } diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java deleted file mode 100644 index 812bb33b1dd6..000000000000 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java +++ /dev/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; - 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 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 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 configurations, final Map 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 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 testFindMatches(String source, MatchOptions options, boolean filePattern) { - return testFindMatches(source, options, filePattern, options.getFileType(), null, false); - } - - class TaskScheduler implements MatchingProcess { - private ArrayList 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 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 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 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 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 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 elementsToProcess = new SmartList<>(); - - for (Language lang : viewProvider.getLanguages()) { - elementsToProcess.add(viewProvider.getPsi(lang)); - } - - return elementsToProcess; - } - ); - } - } -} diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java index 64f7f02ef788..9591539ede9e 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java @@ -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); diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java index 1481dbbd79a3..d3070c511597 100644 --- a/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java +++ b/platform/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java @@ -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) { diff --git a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTestCase.java b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTestCase.java index 57ce2c8b31c7..2231c0f2b63e 100644 --- a/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTestCase.java +++ b/platform/structuralsearch/testSource/com/intellij/structuralsearch/StructuralSearchTestCase.java @@ -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); }