1 package com.intellij.structuralsearch.impl.matcher;
3 import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
4 import com.intellij.dupLocator.iterators.NodeIterator;
5 import com.intellij.lang.Language;
6 import com.intellij.lang.StdLanguages;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.ModalityState;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.editor.Document;
11 import com.intellij.openapi.fileTypes.FileType;
12 import com.intellij.openapi.fileTypes.FileTypes;
13 import com.intellij.openapi.fileTypes.LanguageFileType;
14 import com.intellij.openapi.progress.ProcessCanceledException;
15 import com.intellij.openapi.progress.ProgressIndicator;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.roots.ContentIterator;
18 import com.intellij.openapi.util.Computable;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.openapi.vfs.VirtualFile;
21 import com.intellij.psi.*;
22 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
23 import com.intellij.psi.search.GlobalSearchScope;
24 import com.intellij.psi.search.LocalSearchScope;
25 import com.intellij.psi.search.SearchScope;
26 import com.intellij.structuralsearch.*;
27 import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
28 import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
29 import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
30 import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
31 import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
32 import com.intellij.structuralsearch.plugin.ui.Configuration;
33 import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink;
34 import com.intellij.util.IncorrectOperationException;
35 import com.intellij.util.PairProcessor;
36 import com.intellij.util.SmartList;
37 import com.intellij.util.indexing.FileBasedIndex;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
41 import java.lang.ref.SoftReference;
42 import java.util.ArrayList;
43 import java.util.LinkedList;
44 import java.util.List;
47 * This class makes program structure tree matching:
49 public class MatcherImpl {
50 private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl");
51 // project being worked on
52 private final Project project;
54 // context of matching
55 private final MatchContext matchContext;
56 private boolean isTesting;
58 // visitor to delegate the real work
59 private final GlobalMatchingVisitor visitor = new GlobalMatchingVisitor();
60 private ProgressIndicator progress;
61 private final TaskScheduler scheduler = new TaskScheduler();
63 private int totalFilesToScan;
64 private int scannedFilesCount;
66 public MatcherImpl(final Project project, final MatchOptions matchOptions) {
67 this.project = project;
68 matchContext = new MatchContext();
69 matchContext.setMatcher(visitor);
71 if (matchOptions != null) {
72 matchContext.setOptions(matchOptions);
73 cacheCompiledPattern(matchOptions, PatternCompiler.compilePattern(project,matchOptions));
77 static class LastMatchData {
78 CompiledPattern lastPattern;
79 MatchOptions lastOptions;
82 private static SoftReference<LastMatchData> lastMatchData;
84 protected MatcherImpl(Project project) {
88 public static void validate(Project project, MatchOptions options) {
89 PsiDocumentManager.getInstance(project).commitAllDocuments();
91 synchronized(MatcherImpl.class) {
92 final LastMatchData data = new LastMatchData();
93 data.lastPattern = PatternCompiler.compilePattern(project, options);
94 data.lastOptions = options;
95 lastMatchData = new SoftReference<LastMatchData>(data);
98 final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
99 profile.checkSearchPattern(project, options);
102 public static class CompiledOptions {
103 public final List<Pair<MatchContext, Configuration>> matchContexts;
105 public CompiledOptions(final List<Pair<MatchContext, Configuration>> matchContexts) {
106 this.matchContexts = matchContexts;
109 public List<Pair<MatchContext, Configuration>> getMatchContexts() {
110 return matchContexts;
114 public static boolean checkIfShouldAttemptToMatch(MatchContext context, NodeIterator matchedNodes) {
115 final CompiledPattern pattern = context.getPattern();
116 final NodeIterator patternNodes = pattern.getNodes();
119 final PsiElement patternNode = patternNodes.current();
120 if (patternNode == null) {
123 final PsiElement matchedNode = matchedNodes.current();
124 if (matchedNode == null) {
127 final MatchingHandler matchingHandler = pattern.getHandler(patternNode);
128 if (matchingHandler == null || !matchingHandler.canMatch(patternNode, matchedNode)) {
131 matchedNodes.advance();
132 patternNodes.advance();
135 patternNodes.reset();
136 matchedNodes.reset();
140 public void processMatchesInElement(MatchContext context, Configuration configuration,
141 NodeIterator matchedNodes,
142 PairProcessor<MatchResult, Configuration> processor) {
144 configureOptions(context, configuration, matchedNodes.current(), processor);
145 context.setShouldRecursivelyMatch(false);
146 visitor.matchContext(matchedNodes);
148 matchedNodes.reset();
152 public void clearContext() {
153 matchContext.clear();
156 private void configureOptions(MatchContext context,
157 final Configuration configuration,
159 final PairProcessor<MatchResult, Configuration> processor) {
160 if (psiFile == null) return;
161 LocalSearchScope scope = new LocalSearchScope(psiFile);
163 matchContext.clear();
164 matchContext.setMatcher(visitor);
166 MatchOptions options = context.getOptions();
167 matchContext.setOptions(options);
168 matchContext.setPattern(context.getPattern());
169 matchContext.setShouldRecursivelyMatch(context.shouldRecursivelyMatch());
170 visitor.setMatchContext(matchContext);
172 matchContext.setSink(
173 new MatchConstraintsSink(
174 new MatchResultSink() {
175 public void newMatch(MatchResult result) {
176 processor.process(result, configuration);
179 public void processFile(PsiFile element) {
182 public void setMatchingProcess(MatchingProcess matchingProcess) {
185 public void matchingFinished() {
188 public ProgressIndicator getProgressIndicator() {
192 options.getMaxMatchesCount(),
193 options.isDistinct(),
194 options.isCaseSensitiveMatch()
197 options.setScope(scope);
200 public CompiledOptions precompileOptions(List<Configuration> configurations) {
201 final List<Pair<MatchContext, Configuration>> contexts = new ArrayList<Pair<MatchContext, Configuration>>();
203 for (final Configuration configuration : configurations) {
204 final MatchContext matchContext = new MatchContext();
205 matchContext.setMatcher(visitor);
206 final MatchOptions matchOptions = configuration.getMatchOptions();
207 matchContext.setOptions(matchOptions);
209 ApplicationManager.getApplication().runReadAction(new Runnable() {
213 CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions);
214 matchContext.setPattern(compiledPattern);
215 contexts.add(Pair.create(matchContext, configuration));
217 catch (UnsupportedPatternException ignored) {}
218 catch (MalformedPatternException ignored) {}
222 return new CompiledOptions(contexts);
225 Project getProject() {
230 * Finds the matches of given pattern starting from given tree element.
231 * @throws MalformedPatternException
232 * @throws UnsupportedPatternException
234 protected void findMatches(MatchResultSink sink, final MatchOptions options) throws MalformedPatternException, UnsupportedPatternException
236 CompiledPattern compiledPattern = prepareMatching(sink, options);
237 if (compiledPattern== null) {
241 matchContext.getSink().setMatchingProcess( scheduler );
243 progress = matchContext.getSink().getProgressIndicator();
245 if (/*TokenBasedSearcher.canProcess(compiledPattern)*/ false) {
246 //TokenBasedSearcher searcher = new TokenBasedSearcher(this);
247 //searcher.search(compiledPattern);
249 matchContext.getSink().matchingFinished();
256 final PsiElement[] elements = ((LocalSearchScope)options.getScope()).getScope();
258 PsiElement parent = elements[0].getParent();
259 if (elements.length > 0 && matchContext.getPattern().getStrategy().continueMatching(parent != null ? parent : elements[0])) {
260 visitor.matchContext(new SsrFilteringNodeIterator(new ArrayBackedNodeIterator(elements)));
263 for (PsiElement element : elements) {
268 matchContext.getSink().matchingFinished();
271 if (!findMatches(options, compiledPattern)) {
276 if (scheduler.getTaskQueueEndAction()==null) {
277 scheduler.setTaskQueueEndAction(
280 matchContext.getSink().matchingFinished();
286 scheduler.executeNext();
289 private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) {
290 LanguageFileType languageFileType = (LanguageFileType)options.getFileType();
291 final Language patternLanguage = languageFileType.getLanguage();
292 final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(patternLanguage);
293 assert profile != null;
294 final Language patternLanguage2 = patternLanguage == StdLanguages.XML ? StdLanguages.XHTML:null;
295 SearchScope searchScope = compiledPattern.getScope();
296 boolean ourOptimizedScope = searchScope != null;
297 if (!ourOptimizedScope) searchScope = options.getScope();
299 if (searchScope instanceof GlobalSearchScope) {
300 final GlobalSearchScope scope = (GlobalSearchScope)searchScope;
302 final ContentIterator ci = new ContentIterator() {
303 public boolean processFile(final VirtualFile fileOrDir) {
304 if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) {
306 scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir, profile, patternLanguage, patternLanguage2));
312 ApplicationManager.getApplication().runReadAction(new Runnable() {
315 FileBasedIndex.getInstance().iterateIndexableFiles(ci, project, progress);
318 progress.setText2("");
321 final PsiElement[] elementsToScan = ((LocalSearchScope)searchScope).getScope();
322 totalFilesToScan = elementsToScan.length;
324 for (int i = 0; i < elementsToScan.length; ++i) {
325 final PsiElement psiElement = elementsToScan[i];
327 if (psiElement == null) continue;
328 final Language language = psiElement.getLanguage();
330 PsiFile file = psiElement instanceof PsiFile ? (PsiFile)psiElement : psiElement.getContainingFile();
332 if (profile.isMyFile(file, language, patternLanguage, patternLanguage2)) {
333 scheduler.addOneTask(new MatchOnePsiFile(psiElement));
335 if (ourOptimizedScope) elementsToScan[i] = null; // to prevent long PsiElement reference
341 private CompiledPattern prepareMatching(final MatchResultSink sink, final MatchOptions options) {
342 CompiledPattern savedPattern = null;
344 if (matchContext.getOptions() == options && matchContext.getPattern() != null &&
345 matchContext.getOptions().hashCode() == matchContext.getPattern().getOptionsHashStamp()) {
346 savedPattern = matchContext.getPattern();
349 matchContext.clear();
350 matchContext.setSink(
351 new MatchConstraintsSink(
353 options.getMaxMatchesCount(),
354 options.isDistinct(),
355 options.isCaseSensitiveMatch()
358 matchContext.setOptions(options);
359 matchContext.setMatcher(visitor);
360 visitor.setMatchContext(matchContext);
362 CompiledPattern compiledPattern = savedPattern;
364 if (compiledPattern == null) {
366 synchronized(getClass()) {
367 final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData);
368 if (data != null && options == data.lastOptions) {
369 compiledPattern = data.lastPattern;
371 lastMatchData = null;
374 if (compiledPattern==null) {
375 compiledPattern = ApplicationManager.getApplication().runReadAction(new Computable<CompiledPattern>() {
377 public CompiledPattern compute() {
378 return PatternCompiler.compilePattern(project,options);
384 cacheCompiledPattern(options, compiledPattern);
385 return compiledPattern;
388 private void cacheCompiledPattern(final MatchOptions options, final CompiledPattern compiledPattern) {
389 matchContext.setPattern(compiledPattern);
390 compiledPattern.setOptionsHashStamp(options.hashCode());
394 * Finds the matches of given pattern starting from given tree element.
395 * @param sink match result destination
396 * @throws MalformedPatternException
397 * @throws UnsupportedPatternException
399 protected void testFindMatches(MatchResultSink sink, MatchOptions options)
400 throws MalformedPatternException, UnsupportedPatternException {
403 findMatches(sink,options);
410 * Finds the matches of given pattern starting from given tree element.
411 * @param source string for search
412 * @param pattern to be searched
413 * @return list of matches found
414 * @throws MalformedPatternException
415 * @throws UnsupportedPatternException
417 protected List<MatchResult> testFindMatches(String source,
419 MatchOptions options,
421 FileType sourceFileType,
422 String sourceExtension,
423 boolean physicalSourceFile)
424 throws MalformedPatternException, UnsupportedPatternException {
426 CollectingMatchResultSink sink = new CollectingMatchResultSink();
429 PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source,
430 filePattern ? PatternTreeContext.File : PatternTreeContext.Block,
433 project, physicalSourceFile);
435 options.setSearchPattern(pattern);
436 options.setScope(new LocalSearchScope(elements));
437 testFindMatches(sink, options);
439 catch (IncorrectOperationException e) {
440 MalformedPatternException exception = new MalformedPatternException();
441 exception.initCause(e);
445 return sink.getMatches();
448 protected List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern) {
449 return testFindMatches(source, pattern, options, filePattern, options.getFileType(), null, false);
452 class TaskScheduler implements MatchingProcess {
453 private LinkedList<Runnable> tasks = new LinkedList<Runnable>();
454 private boolean ended;
455 private Runnable taskQueueEndAction;
457 private boolean suspended;
463 public void pause() {
467 public void resume() {
468 if (!suspended) return;
473 public boolean isSuspended() {
477 public boolean isEnded() {
481 void setTaskQueueEndAction(Runnable taskQueueEndAction) {
482 this.taskQueueEndAction = taskQueueEndAction;
484 Runnable getTaskQueueEndAction () {
485 return taskQueueEndAction;
488 void addOneTask(Runnable runnable) {
492 private void executeNext() {
493 while(!suspended && !ended) {
494 if (tasks.isEmpty()) {
499 final Runnable task = tasks.removeFirst();
503 catch (ProcessCanceledException e) {
508 catch (StructuralSearchException e) {
513 catch (Throwable th) {
518 if (ended) clearSchedule();
521 private void init() {
524 PsiManager.getInstance(project).startBatchFilesProcessingMode();
527 private void clearSchedule() {
529 taskQueueEndAction.run();
530 if (!project.isDisposed()) {
531 PsiManager.getInstance(project).finishBatchFilesProcessingMode();
539 private class MatchOnePsiFile extends MatchOneFile {
540 private PsiElement file;
542 MatchOnePsiFile(PsiElement file) {
548 protected List<PsiElement> getPsiElementsToProcess() {
549 PsiElement file = this.file;
551 return new SmartList<PsiElement>(file);
555 private abstract class MatchOneFile implements Runnable {
557 List<PsiElement> files = getPsiElementsToProcess();
559 if (progress!=null) {
560 progress.setFraction((double)scannedFilesCount/totalFilesToScan);
565 if (files == null || files.size() == 0) return;
566 final PsiFile psiFile = files.get(0).getContainingFile();
569 final Runnable action = new Runnable() {
571 ApplicationManager.getApplication().runWriteAction(new Runnable() {
573 if (project.isDisposed()) return;
574 final PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
575 Document document = manager.getDocument(psiFile);
576 if (document != null) manager.commitDocument(document);
582 if (ApplicationManager.getApplication().isDispatchThread()) {
585 ApplicationManager.getApplication().invokeAndWait(
587 ModalityState.defaultModalityState()
592 if (project.isDisposed()) return;
594 for(PsiElement file:files) {
595 if (file instanceof PsiFile) {
596 matchContext.getSink().processFile((PsiFile)file);
599 final PsiElement finalFile = file;
600 ApplicationManager.getApplication().runReadAction(
603 PsiElement file = finalFile;
604 if (!file.isValid()) return;
605 file = StructuralSearchUtil.getProfileByLanguage(file.getLanguage()).extendMatchOnePsiFile(file);
613 protected abstract @Nullable List<PsiElement> getPsiElementsToProcess();
616 // Initiates the matching process for given element
617 // @param element the current search tree element
618 public void match(PsiElement element) {
619 MatchingStrategy strategy = matchContext.getPattern().getStrategy();
621 if (strategy.continueMatching(element)) {
622 visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element}));
625 for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) {
628 if (element instanceof PsiLanguageInjectionHost) {
629 InjectedLanguageUtil.enumerate(element, new PsiLanguageInjectionHost.InjectedPsiVisitor() {
631 public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) {
639 protected MatchResult isMatchedByDownUp(PsiElement element, final MatchOptions options) {
640 final CollectingMatchResultSink sink = new CollectingMatchResultSink();
641 CompiledPattern compiledPattern = prepareMatching(sink, options);
643 if (compiledPattern== null) {
648 PsiElement targetNode = compiledPattern.getTargetNode();
649 PsiElement elementToStartMatching = null;
651 if (targetNode == null) {
652 targetNode = compiledPattern.getNodes().current();
653 if (targetNode != null) {
654 compiledPattern.getNodes().advance();
655 assert !compiledPattern.getNodes().hasNext();
656 compiledPattern.getNodes().rewind();
658 while (element.getClass() != targetNode.getClass()) {
659 element = element.getParent();
660 if (element == null) return null;
663 elementToStartMatching = element;
666 targetNode = StructuralSearchUtil.getProfileByPsiElement(element).extendMatchedByDownUp(targetNode);
668 MatchingHandler handler = null;
670 while (element.getClass() == targetNode.getClass() ||
671 compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element)
673 handler = compiledPattern.getHandler(targetNode);
674 handler.setPinnedElement(element);
675 elementToStartMatching = element;
676 if (handler instanceof TopLevelMatchingHandler) break;
677 element = element.getParent();
678 targetNode = targetNode.getParent();
680 if (options.isLooseMatching()) {
681 element = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(element);
682 targetNode = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(targetNode);
686 if (!(handler instanceof TopLevelMatchingHandler)) return null;
689 assert targetNode != null : "Could not match down up when no target node";
691 match(elementToStartMatching);
692 matchContext.getSink().matchingFinished();
693 final int matchCount = sink.getMatches().size();
694 assert matchCount <= 1;
695 return matchCount > 0 ? sink.getMatches().get(0) : null;
698 private class MatchOneVirtualFile extends MatchOneFile {
699 private final VirtualFile myFileOrDir;
700 private final StructuralSearchProfile myProfile;
701 private final Language myOurPatternLanguage;
702 private final Language myOurPatternLanguage2;
704 public MatchOneVirtualFile(VirtualFile fileOrDir,
705 StructuralSearchProfile profile,
706 Language ourPatternLanguage,
707 Language ourPatternLanguage2) {
708 myFileOrDir = fileOrDir;
710 myOurPatternLanguage = ourPatternLanguage;
711 myOurPatternLanguage2 = ourPatternLanguage2;
716 protected List<PsiElement> getPsiElementsToProcess() {
717 return ApplicationManager.getApplication().runReadAction(new Computable<List<PsiElement>>() {
719 public List<PsiElement> compute() {
720 PsiFile file = PsiManager.getInstance(project).findFile(myFileOrDir);
725 final FileViewProvider viewProvider = file.getViewProvider();
726 List<PsiElement> elementsToProcess = new SmartList<PsiElement>();
728 for(Language lang: viewProvider.getLanguages()) {
729 if (myProfile.isMyFile(file, lang, myOurPatternLanguage, myOurPatternLanguage2)) {
730 elementsToProcess.add(viewProvider.getPsi(lang));
734 return elementsToProcess;