summaryrefslogtreecommitdiff
path: root/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher
diff options
context:
space:
mode:
Diffstat (limited to 'platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher')
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java182
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java315
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java85
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java130
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java207
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java42
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java737
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java67
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java8
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java22
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java130
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java71
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java59
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java103
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java314
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java27
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java85
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java512
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java436
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java70
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java53
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java27
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java16
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java51
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java43
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java19
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java8
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java46
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java49
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java27
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java271
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java20
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java108
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java557
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java21
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java85
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java21
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java23
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java20
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java22
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java38
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java18
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java24
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java31
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java175
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java28
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java91
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java38
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java13
-rw-r--r--platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java42
52 files changed, 5619 insertions, 0 deletions
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java
new file mode 100644
index 000000000000..565f80ac01ce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java
@@ -0,0 +1,182 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.openapi.util.Key;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.search.SearchScope;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SimpleHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class to hold compiled pattern information
+ */
+public abstract class CompiledPattern {
+ public static final String ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME = "__class_unmatched__";
+ private SearchScope scope;
+ private NodeIterator nodes;
+ private MatchingStrategy strategy;
+ private PsiElement targetNode;
+ private int optionsHashStamp;
+ private int nodeCount;
+
+ // @todo support this property during matching (how many nodes should be advanced)
+ // when match is not successful (or successful partially)
+ //private int advancement = 1;
+
+ public abstract String[] getTypedVarPrefixes();
+ public abstract boolean isTypedVar(String str);
+
+ public void setTargetNode(final PsiElement element) {
+ targetNode = element;
+ }
+
+ public PsiElement getTargetNode() {
+ return targetNode;
+ }
+
+ public int getOptionsHashStamp() {
+ return optionsHashStamp;
+ }
+
+ public void setOptionsHashStamp(final int optionsHashStamp) {
+ this.optionsHashStamp = optionsHashStamp;
+ }
+
+ public static final Key<Object> HANDLER_KEY = Key.create("ss.handler");
+
+ public MatchingStrategy getStrategy() {
+ return strategy;
+ }
+
+ public void setStrategy(MatchingStrategy strategy) {
+ this.strategy = strategy;
+ }
+
+ public int getNodeCount() {
+ return nodeCount;
+ }
+
+ public NodeIterator getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(List<PsiElement> elements) {
+ this.nodes = new ArrayBackedNodeIterator(PsiUtilCore.toPsiElementArray(elements));
+ this.nodeCount = elements.size();
+ }
+
+ public boolean isTypedVar(final PsiElement element) {
+ return element!=null && isTypedVar( element.getText() );
+ }
+
+ public boolean isRealTypedVar(PsiElement element) {
+ if (element!=null && element.getTextLength()>0) {
+ String str = getTypedVarString(element);
+ if (str.length()==0) {
+ return false;
+ }
+ return isTypedVar( str );
+ } else {
+ return false;
+ }
+ }
+
+ public String getTypedVarString(PsiElement element) {
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile == null) {
+ return element.getText();
+ }
+ return profile.getTypedVarString(element);
+ }
+
+ private final HashMap<Object,MatchingHandler> handlers = new HashMap<Object,MatchingHandler>();
+
+ public MatchingHandler getHandlerSimple(PsiElement node) {
+ return handlers.get(node);
+ }
+
+ private PsiElement last;
+ private MatchingHandler lastHandler;
+
+ public MatchingHandler getHandler(PsiElement node) {
+ if (node==last) {
+ return lastHandler;
+ }
+ MatchingHandler handler = handlers.get(node);
+
+ if (handler==null) {
+ handler = new SimpleHandler();
+ setHandler(node,handler);
+ }
+
+ last = node;
+ lastHandler = handler;
+
+ return handler;
+ }
+
+ public MatchingHandler getHandler(String name) {
+ return handlers.get(name);
+ }
+
+ public void setHandler(PsiElement node, MatchingHandler handler) {
+ last = null;
+ handlers.put(node,handler);
+ }
+
+ public SubstitutionHandler createSubstitutionHandler(
+ String name, String compiledName, boolean target,int minOccurs, int maxOccurs, boolean greedy) {
+
+ SubstitutionHandler handler = (SubstitutionHandler) handlers.get(compiledName);
+ if (handler != null) return handler;
+
+ handler = doCreateSubstitutionHandler(name, target, minOccurs, maxOccurs, greedy);
+
+ handlers.put(compiledName,handler);
+
+ return handler;
+ }
+
+ protected SubstitutionHandler doCreateSubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) {
+ return new SubstitutionHandler(name, target, minOccurs, maxOccurs, greedy);
+ }
+
+ public SearchScope getScope() {
+ return scope;
+ }
+
+ public void setScope(SearchScope scope) {
+ this.scope = scope;
+ }
+
+ public void clearHandlers() {
+ handlers.clear();
+ last = null;
+ lastHandler = null;
+ }
+
+ void clearHandlersState() {
+ for (final MatchingHandler h : handlers.values()) {
+ if (h != null) h.reset();
+ }
+ }
+
+ public boolean isToResetHandler(PsiElement element) {
+ return true;
+ }
+
+ @Nullable
+ public String getAlternativeTextToMatch(PsiElement node, String previousText) {
+ return null;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java
new file mode 100644
index 000000000000..e7327fad7e5e
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 07.01.2004
+ * Time: 1:06:51
+ * To change this template use Options | File Templates.
+ */
+public interface DataProvider {
+ Editor getEditor();
+ Project getProject();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java
new file mode 100644
index 000000000000..5577db0d8540
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java
@@ -0,0 +1,315 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.dupLocator.AbstractMatchingVisitor;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.lang.Language;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiElementVisitor;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.DelegatingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import com.intellij.util.containers.HashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Visitor class to manage pattern matching
+ */
+@SuppressWarnings({"RefusedBequest"})
+public class GlobalMatchingVisitor extends AbstractMatchingVisitor {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor");
+
+ // the pattern element for visitor check
+ private PsiElement myElement;
+
+ // the result of matching in visitor
+ private boolean myResult;
+
+ // context of matching
+ private MatchContext matchContext;
+
+ private MatchingHandler myLastHandler;
+
+ private Map<Language, PsiElementVisitor> myLanguage2MatchingVisitor = new HashMap<Language, PsiElementVisitor>(1);
+
+ public PsiElement getElement() {
+ return myElement;
+ }
+
+ public boolean getResult() {
+ return myResult;
+ }
+
+ public void setResult(boolean result) {
+ this.myResult = result;
+ }
+
+ public MatchContext getMatchContext() {
+ return matchContext;
+ }
+
+ @Override
+ protected boolean doMatchInAnyOrder(NodeIterator elements, NodeIterator elements2) {
+ return matchContext.getPattern().getHandler(elements.current()).matchInAnyOrder(
+ elements,
+ elements2,
+ matchContext
+ );
+ }
+
+ @NotNull
+ @Override
+ protected NodeFilter getNodeFilter() {
+ return LexicalNodesFilter.getInstance();
+ }
+
+ public final boolean handleTypedElement(final PsiElement typedElement, final PsiElement match) {
+ MatchingHandler handler = matchContext.getPattern().getHandler(typedElement);
+ final MatchingHandler initialHandler = handler;
+ if (handler instanceof DelegatingHandler) {
+ handler = ((DelegatingHandler)handler).getDelegate();
+ }
+ assert handler instanceof SubstitutionHandler :
+ handler != null ? handler.getClass() : "null" + ' ' + (initialHandler != null ? initialHandler.getClass() : "null");
+
+ return ((SubstitutionHandler)handler).handle(match, matchContext);
+ }
+
+ /**
+ * Identifies the match between given element of program tree and pattern element
+ *
+ * @param el1 the pattern for matching
+ * @param el2 the tree element for matching
+ * @return true if equal and false otherwise
+ */
+ public boolean match(final PsiElement el1, final PsiElement el2) {
+ // null
+ if (el1 == el2) return true;
+ if (el2 == null || el1 == null) {
+ // this a bug!
+ return false;
+ }
+
+ // copy changed data to local stack
+ PsiElement prevElement = myElement;
+ myElement = el2;
+
+ try {
+ /*if (el1 instanceof XmlElement) {
+ el1.accept(myXmlVisitor);
+ }
+ else {
+ el1.accept(myJavaVisitor);
+ }*/
+ PsiElementVisitor visitor = getVisitorForElement(el1);
+ if (visitor != null) {
+ el1.accept(visitor);
+ }
+ }
+ catch (ClassCastException ex) {
+ myResult = false;
+ }
+ finally {
+ myElement = prevElement;
+ }
+
+ return myResult;
+ }
+
+ @Nullable
+ private PsiElementVisitor getVisitorForElement(PsiElement element) {
+ Language language = element.getLanguage();
+ PsiElementVisitor visitor = myLanguage2MatchingVisitor.get(language);
+ if (visitor == null) {
+ visitor = createMatchingVisitor(language);
+ myLanguage2MatchingVisitor.put(language, visitor);
+ }
+ return visitor;
+ }
+
+ @Nullable
+ private PsiElementVisitor createMatchingVisitor(Language language) {
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ if (profile == null) {
+ LOG.warn("there is no StructuralSearchProfile for language " + language.getID());
+ return null;
+ }
+ else {
+ return profile.createMatchingVisitor(this);
+ }
+ }
+
+ /**
+ * Matches tree segments starting with given elements to find equality
+ *
+ * @param nodes the pattern element for matching
+ * @param nodes2 the tree element for matching
+ * @return if they are equal and false otherwise
+ */
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) {
+ if (!nodes.hasNext()) {
+ return nodes.hasNext() == nodes2.hasNext();
+ }
+
+ myLastHandler = matchContext.getPattern().getHandler(nodes.current());
+ return myLastHandler.matchSequentially(
+ nodes,
+ nodes2,
+ matchContext
+ );
+ }
+
+ public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) {
+ if (!nodes.hasNext()) {
+ return nodes.hasNext() == nodes2.hasNext();
+ }
+
+ return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(
+ nodes,
+ nodes2,
+ matchContext
+ );
+ }
+
+ /**
+ * Descents the tree in depth finding matches
+ *
+ * @param elements the element for which the sons are looked for match
+ */
+ public void matchContext(final NodeIterator elements) {
+ final CompiledPattern pattern = matchContext.getPattern();
+ final NodeIterator patternNodes = pattern.getNodes().clone();
+ final MatchResultImpl saveResult = matchContext.hasResult() ? matchContext.getResult() : null;
+ final List<PsiElement> saveMatchedNodes = matchContext.getMatchedNodes();
+
+ try {
+ matchContext.setResult(null);
+ matchContext.setMatchedNodes(null);
+
+ if (!patternNodes.hasNext()) return;
+ final MatchingHandler firstMatchingHandler = pattern.getHandler(patternNodes.current());
+
+ for (; elements.hasNext(); elements.advance()) {
+ final PsiElement elementNode = elements.current();
+
+ boolean matched = firstMatchingHandler.matchSequentially(patternNodes, elements, matchContext);
+
+ if (matched) {
+ MatchingHandler matchingHandler = matchContext.getPattern().getHandler(Configuration.CONTEXT_VAR_NAME);
+ if (matchingHandler != null) {
+ matched = ((SubstitutionHandler)matchingHandler).handle(elementNode, matchContext);
+ }
+ }
+
+ final List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
+
+ if (matched) {
+ dispatchMatched(matchedNodes, matchContext.getResult());
+ }
+
+ matchContext.setMatchedNodes(null);
+ matchContext.setResult(null);
+
+ patternNodes.reset();
+ if (matchedNodes != null && matchedNodes.size() > 0 && matched) {
+ elements.rewind();
+ }
+ }
+ }
+ finally {
+ matchContext.setResult(saveResult);
+ matchContext.setMatchedNodes(saveMatchedNodes);
+ }
+ }
+
+ private void dispatchMatched(final List<PsiElement> matchedNodes, MatchResultImpl result) {
+ if (!matchContext.getOptions().isResultIsContextMatch() && doDispatch(result, result)) return;
+
+ // There is no substitutions so show the context
+
+ processNoSubstitutionMatch(matchedNodes, result);
+ matchContext.getSink().newMatch(result);
+ }
+
+ private boolean doDispatch(final MatchResultImpl result, MatchResultImpl context) {
+ boolean ret = false;
+
+ for (MatchResult _r : result.getAllSons()) {
+ final MatchResultImpl r = (MatchResultImpl)_r;
+
+ if ((r.isScopeMatch() && !r.isTarget()) || r.isMultipleMatch()) {
+ ret |= doDispatch(r, context);
+ }
+ else if (r.isTarget()) {
+ r.setContext(context);
+ matchContext.getSink().newMatch(r);
+ ret = true;
+ }
+ }
+ return ret;
+ }
+
+ private static void processNoSubstitutionMatch(List<PsiElement> matchedNodes, MatchResultImpl result) {
+ boolean complexMatch = matchedNodes.size() > 1;
+ final PsiElement match = matchedNodes.get(0);
+
+ if (!complexMatch) {
+ result.setMatchRef(new SmartPsiPointer(match));
+ result.setMatchImage(match.getText());
+ }
+ else {
+ MatchResultImpl sonresult;
+
+ for (final PsiElement matchStatement : matchedNodes) {
+ result.getMatches().add(
+ sonresult = new MatchResultImpl(
+ MatchResult.LINE_MATCH,
+ matchStatement.getText(),
+ new SmartPsiPointer(matchStatement),
+ true
+ )
+ );
+
+ sonresult.setParent(result);
+ }
+
+ result.setMatchRef(
+ new SmartPsiPointer(match)
+ );
+ result.setMatchImage(
+ match.getText()
+ );
+ result.setName(MatchResult.MULTI_LINE_MATCH);
+ }
+ }
+
+ public void setMatchContext(MatchContext matchContext) {
+ this.matchContext = matchContext;
+ }
+
+ // Matches the sons of given elements to find equality
+ // @param el1 the pattern element for matching
+ // @param el2 the tree element for matching
+ // @return if they are equal and false otherwise
+
+ @Override
+ protected boolean isLeftLooseMatching() {
+ return matchContext.getOptions().isLooseMatching();
+ }
+
+ @Override
+ protected boolean isRightLooseMatching() {
+ return false;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java
new file mode 100644
index 000000000000..2036c83d56f4
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java
@@ -0,0 +1,85 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResultSink;
+import com.intellij.structuralsearch.MatchingProcess;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.psi.PsiFile;
+import com.intellij.openapi.progress.ProgressIndicator;
+
+import javax.swing.*;
+import java.util.HashMap;
+
+/**
+ * Sink to detect
+ */
+class MatchConstraintsSink implements MatchResultSink {
+ private final MatchResultSink delegate;
+ private MatchingProcess process;
+ private final boolean distinct;
+ private final boolean caseSensitive;
+ private int matchCount;
+ private final int maxMatches;
+ private final HashMap<Object, MatchResult> matches = new HashMap<Object, MatchResult>();
+
+ MatchConstraintsSink(MatchResultSink _delegate, int _maxMatches,boolean distinct, boolean _caseSensitive) {
+ delegate = _delegate;
+ maxMatches = _maxMatches;
+ this.distinct = distinct;
+ caseSensitive = _caseSensitive;
+ }
+
+ public void newMatch(MatchResult result) {
+ if (distinct) {
+ String matchImage = result.getMatchImage();
+
+ if (!caseSensitive) matchImage = matchImage.toLowerCase();
+
+ if (matches.get(matchImage)!=null) {
+ return;
+ }
+
+ matches.put(matchImage,result);
+ }
+ else {
+ final PsiElement match = result.getMatch();
+ if (matches.containsKey(match)) {
+ return;
+ }
+ matches.put(match, result);
+ }
+
+ delegate.newMatch(result);
+ ++matchCount;
+
+ if (matchCount==maxMatches) {
+ JOptionPane.showMessageDialog(null, SSRBundle.message("search.produced.too.many.results.message"));
+ process.stop();
+ }
+ }
+
+ /* Notifies sink about starting the matching for given element
+ * @param element the current file
+ * @param task process continuation reference
+ */
+ public void processFile(PsiFile element) {
+ delegate.processFile(element);
+ }
+
+ public void setMatchingProcess(MatchingProcess matchingProcess) {
+ process = matchingProcess;
+ delegate.setMatchingProcess(process);
+ }
+
+ public void matchingFinished() {
+ matchCount = 0;
+ matches.clear();
+ delegate.matchingFinished();
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return delegate.getProgressIndicator();
+ }
+
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java
new file mode 100644
index 000000000000..c4c8d21e3ab3
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java
@@ -0,0 +1,130 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.MatchResultSink;
+import com.intellij.util.containers.Stack;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Global context of matching process
+ */
+public class MatchContext {
+ private MatchResultSink sink;
+ private final Stack<MatchResultImpl> previousResults = new Stack<MatchResultImpl>();
+ private MatchResultImpl result;
+ private CompiledPattern pattern;
+ private MatchOptions options;
+ private GlobalMatchingVisitor matcher;
+ private boolean shouldRecursivelyMatch = true;
+ private boolean myWithAlternativePatternRoots = true;
+
+ private List<PsiElement> myMatchedNodes;
+
+ public List<PsiElement> getMatchedNodes() {
+ return myMatchedNodes;
+ }
+
+ public void setMatchedNodes(final List<PsiElement> matchedNodes) {
+ myMatchedNodes = matchedNodes;
+ }
+
+ public boolean isWithAlternativePatternRoots() {
+ return myWithAlternativePatternRoots;
+ }
+
+ public void setWithAlternativePatternRoots(boolean withAlternativePatternRoots) {
+ myWithAlternativePatternRoots = withAlternativePatternRoots;
+ }
+
+ public interface MatchedElementsListener {
+ void matchedElements(Collection<PsiElement> matchedElements);
+ void commitUnmatched();
+ }
+
+ private MatchedElementsListener myMatchedElementsListener;
+
+ public void setMatcher(GlobalMatchingVisitor matcher) {
+ this.matcher = matcher;
+ }
+
+ public GlobalMatchingVisitor getMatcher() {
+ return matcher;
+ }
+
+ public MatchOptions getOptions() {
+ return options;
+ }
+
+ public void setOptions(MatchOptions options) {
+ this.options = options;
+ }
+
+ public MatchResultImpl getPreviousResult() {
+ return previousResults.isEmpty() ? null : previousResults.peek();
+ }
+
+ public MatchResultImpl getResult() {
+ if (result==null) result = new MatchResultImpl();
+ return result;
+ }
+
+ public void pushResult() {
+ previousResults.push(result);
+ result = null;
+ }
+
+ public void popResult() {
+ result = previousResults.pop();
+ }
+
+ public void setResult(MatchResultImpl result) {
+ this.result = result;
+ if (result == null) {
+ pattern.clearHandlersState();
+ }
+ }
+
+ public boolean hasResult() {
+ return result!=null;
+ }
+
+ public CompiledPattern getPattern() {
+ return pattern;
+ }
+
+ public void setPattern(CompiledPattern pattern) {
+ this.pattern = pattern;
+ }
+
+ public MatchResultSink getSink() {
+ return sink;
+ }
+
+ public void setSink(MatchResultSink sink) {
+ this.sink = sink;
+ }
+
+ void clear() {
+ result = null;
+ pattern = null;
+ }
+
+ public boolean shouldRecursivelyMatch() {
+ return shouldRecursivelyMatch;
+ }
+
+ public void setShouldRecursivelyMatch(boolean shouldRecursivelyMatch) {
+ this.shouldRecursivelyMatch = shouldRecursivelyMatch;
+ }
+
+ public void setMatchedElementsListener(MatchedElementsListener _matchedElementsListener) {
+ myMatchedElementsListener = _matchedElementsListener;
+ }
+
+ public MatchedElementsListener getMatchedElementsListener() {
+ return myMatchedElementsListener;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java
new file mode 100644
index 000000000000..b75ffb5d0f5d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.MatchVariableConstraint;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+
+import java.util.Set;
+
+public abstract class MatchPredicateProvider {
+ public static final ExtensionPointName<MatchPredicateProvider> EP_NAME = ExtensionPointName.create("com.intellij.structuralsearch.matchPredicateProvider");
+ public abstract void collectPredicates(MatchVariableConstraint constraint,
+ String name,
+ MatchOptions options,
+ Set<MatchPredicate> predicates);
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java
new file mode 100644
index 000000000000..8761472d813a
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java
@@ -0,0 +1,207 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class describing the match result
+ */
+public final class MatchResultImpl extends MatchResult {
+ private String name;
+ private SmartPsiPointer matchRef;
+ private int start;
+ private int end = -1;
+ private String matchImage;
+ private List<MatchResult> matches;
+ private MatchResult parent;
+ private boolean target;
+
+ @NonNls public static final String DEFAULT_NAME2 = "end of context match";
+ @NonNls public static final String DEFAULT_NAME = "start of context match";
+ private boolean myScopeMatch;
+ private boolean myMultipleMatch;
+ @NonNls private static final String NULL = "null";
+ private MatchResultImpl myContext;
+
+ MatchResultImpl() {
+ }
+
+ public MatchResultImpl(String name, String image, SmartPsiPointer ref, boolean target) {
+ this(name,image,ref,0,-1,target);
+ }
+
+ public MatchResultImpl(String name, String image, SmartPsiPointer ref, int start, int end,boolean target) {
+ matchRef = ref;
+ this.name = name;
+ matchImage = image;
+ this.target = target;
+ this.start = start;
+ this.end = end;
+ }
+
+ public String getMatchImage() {
+ if (matchImage==null) {
+ matchImage = NULL;
+ }
+ return matchImage;
+ }
+
+ public void setParent(MatchResult parent) {
+ this.parent = parent;
+ }
+
+ public SmartPsiPointer getMatchRef() {
+ return matchRef;
+ }
+
+ public PsiElement getMatch() {
+ return matchRef.getElement();
+ }
+
+ public void setMatchRef(SmartPsiPointer matchStart) {
+ matchRef = matchStart;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List<MatchResult> getMatches() {
+ if (matches==null) matches = new ArrayList<MatchResult>();
+ return matches;
+ }
+
+ public List<MatchResult> getAllSons() {
+ return getMatches();
+ }
+
+ public boolean hasSons() {
+ return matches!=null;
+ }
+
+ public boolean isScopeMatch() {
+ return myScopeMatch;
+ }
+
+ public boolean isMultipleMatch() {
+ return myMultipleMatch;
+ }
+
+ public void clear() {
+ if (matchRef != null) {
+ matchRef.clear();
+ matchRef = null;
+ }
+
+ if (matches != null) {
+ for (final MatchResult match : matches) {
+ ((MatchResultImpl)match).clear();
+ }
+ matches = null;
+ }
+
+ name = null;
+ matchImage = null;
+ }
+
+ public void clearMatches() {
+ matches = null;
+ }
+
+ public void setScopeMatch(final boolean scopeMatch) {
+ myScopeMatch = scopeMatch;
+ }
+
+ public void setMultipleMatch(final boolean multipleMatch) {
+ myMultipleMatch = multipleMatch;
+ }
+
+ public MatchResultImpl findSon(String name) {
+ if (matches!=null) {
+ // @todo this could be performance bottleneck, replace with hash lookup!
+ for (final MatchResult match : matches) {
+ final MatchResultImpl res = (MatchResultImpl)match;
+
+ if (name.equals(res.getName())) {
+ return res;
+ }
+ }
+ }
+ return null;
+ }
+
+ public MatchResultImpl removeSon(String typedVar) {
+ if (matches == null) return null;
+
+ // @todo this could be performance bottleneck, replace with hash lookup!
+ for(Iterator<MatchResult> i=matches.iterator();i.hasNext();) {
+ final MatchResultImpl res = (MatchResultImpl)i.next();
+ if (typedVar.equals(res.getName())) {
+ i.remove();
+ return res;
+ }
+ }
+
+ return null;
+ }
+
+ public void addSon(MatchResultImpl result) {
+ getMatches().add(result);
+ }
+
+ public void setMatchImage(String matchImage) {
+ this.matchImage = matchImage;
+ }
+
+ public boolean isTarget() {
+ return target;
+ }
+
+ public void setTarget(boolean target) {
+ this.target = target;
+ }
+
+ public boolean isMatchImageNull() {
+ return matchImage==null;
+ }
+
+ public int getStart() {
+ return start;
+ }
+
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ public int getEnd() {
+ return end;
+ }
+
+ public void setEnd(int end) {
+ this.end = end;
+ }
+
+ public void setContext(final MatchResultImpl context) {
+ myContext = context;
+ }
+
+ public MatchResultImpl getContext() {
+ return myContext;
+ }
+
+ @Override
+ public String toString() {
+ return "MatchResultImpl{name='" + name + '\'' + ", matchImage='" + matchImage + '\'' + "}";
+ }
+}
+
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java
new file mode 100644
index 000000000000..38c31d16524f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java
@@ -0,0 +1,42 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.*;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 24.12.2003
+ * Time: 22:10:20
+ * To change this template use Options | File Templates.
+ */
+public class MatchUtils {
+ public static final String SPECIAL_CHARS = "*(){}[]^$\\.-|";
+
+ public static final boolean compareWithNoDifferenceToPackage(final String typeImage,@NonNls final String typeImage2) {
+ if (typeImage == null || typeImage2 == null) return typeImage == typeImage2;
+ return typeImage2.endsWith(typeImage) && (
+ typeImage.length() == typeImage2.length() ||
+ typeImage2.charAt(typeImage2.length()-typeImage.length()-1)=='.' // package separator
+ );
+ }
+
+ public static PsiElement getReferencedElement(final PsiElement element) {
+ if (element instanceof PsiReference) {
+ return ((PsiReference)element).resolve();
+ }
+
+ /*if (element instanceof PsiTypeElement) {
+ PsiType type = ((PsiTypeElement)element).getType();
+
+ if (type instanceof PsiArrayType) {
+ type = ((PsiArrayType)type).getComponentType();
+ }
+ if (type instanceof PsiClassType) {
+ return ((PsiClassType)type).resolve();
+ }
+ return null;
+ }*/
+ return element;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java
new file mode 100644
index 000000000000..d667b068ee48
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java
@@ -0,0 +1,737 @@
+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.lang.StdLanguages;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+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.Project;
+import com.intellij.openapi.roots.ContentIterator;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
+import com.intellij.psi.search.DelegatingGlobalSearchScope;
+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.util.IncorrectOperationException;
+import com.intellij.util.PairProcessor;
+import com.intellij.util.SmartList;
+import com.intellij.util.indexing.FileBasedIndex;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 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;
+
+ // 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));
+ }
+ }
+
+ static class LastMatchData {
+ CompiledPattern lastPattern;
+ MatchOptions lastOptions;
+ }
+
+ private static SoftReference<LastMatchData> lastMatchData;
+
+ protected MatcherImpl(Project project) {
+ this(project, null);
+ }
+
+ public static void validate(Project project, MatchOptions options) {
+ PsiDocumentManager.getInstance(project).commitAllDocuments();
+
+ synchronized(MatcherImpl.class) {
+ final LastMatchData data = new LastMatchData();
+ data.lastPattern = PatternCompiler.compilePattern(project, options);
+ data.lastOptions = options;
+ lastMatchData = new SoftReference<LastMatchData>(data);
+ }
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType());
+ profile.checkSearchPattern(project, options);
+ }
+
+ public static class CompiledOptions {
+ public final List<Pair<MatchContext, Configuration>> matchContexts;
+
+ public CompiledOptions(final List<Pair<MatchContext, Configuration>> matchContexts) {
+ this.matchContexts = matchContexts;
+ }
+
+ public List<Pair<MatchContext, Configuration>> getMatchContexts() {
+ return matchContexts;
+ }
+ }
+
+ 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)) {
+ 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();
+ }
+ }
+
+ 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 MatchConstraintsSink(
+ new MatchResultSink() {
+ public void newMatch(MatchResult result) {
+ processor.process(result, configuration);
+ }
+
+ public void processFile(PsiFile element) {
+ }
+
+ public void setMatchingProcess(MatchingProcess matchingProcess) {
+ }
+
+ public void matchingFinished() {
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return null;
+ }
+ },
+ options.getMaxMatchesCount(),
+ options.isDistinct(),
+ options.isCaseSensitiveMatch()
+ )
+ );
+ options.setScope(scope);
+ }
+
+ public CompiledOptions precompileOptions(List<Configuration> configurations) {
+ List<Pair<MatchContext, Configuration>> contexts = new ArrayList<Pair<MatchContext, Configuration>>();
+
+ for (Configuration configuration : configurations) {
+ MatchContext matchContext = new MatchContext();
+ matchContext.setMatcher(visitor);
+ MatchOptions matchOptions = configuration.getMatchOptions();
+ matchContext.setOptions(matchOptions);
+
+ try {
+ CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions);
+ matchContext.setPattern(compiledPattern);
+ contexts.add(Pair.create(matchContext, configuration));
+ }
+ catch (UnsupportedPatternException ignored) {}
+ catch (MalformedPatternException ignored) {}
+ }
+ return new CompiledOptions(contexts);
+ }
+
+ Project getProject() {
+ return project;
+ }
+
+ /**
+ * 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 (/*TokenBasedSearcher.canProcess(compiledPattern)*/ false) {
+ //TokenBasedSearcher searcher = new TokenBasedSearcher(this);
+ //searcher.search(compiledPattern);
+ if (isTesting) {
+ matchContext.getSink().matchingFinished();
+ return;
+ }
+ }
+ else {
+ 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 {
+ for (PsiElement element : elements) {
+ match(element);
+ }
+ }
+
+ matchContext.getSink().matchingFinished();
+ return;
+ }
+ if (!findMatches(options, compiledPattern)) {
+ return;
+ }
+ }
+
+ if (scheduler.getTaskQueueEndAction()==null) {
+ scheduler.setTaskQueueEndAction(
+ new Runnable() {
+ public void run() {
+ matchContext.getSink().matchingFinished();
+ }
+ }
+ );
+ }
+
+ scheduler.executeNext();
+ }
+
+ private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) {
+ LanguageFileType languageFileType = (LanguageFileType)options.getFileType();
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(languageFileType.getLanguage());
+ assert profile != null;
+ PsiElement node = compiledPattern.getNodes().current();
+ final Language ourPatternLanguage = node != null ? profile.getLanguage(node) : ((LanguageFileType)options.getFileType()).getLanguage();
+ final Language ourPatternLanguage2 = ourPatternLanguage == StdLanguages.XML ? StdLanguages.XHTML:null;
+ SearchScope searchScope = compiledPattern.getScope();
+ boolean ourOptimizedScope = searchScope != null;
+ if (!ourOptimizedScope) searchScope = options.getScope();
+
+ if (searchScope instanceof GlobalSearchScope) {
+ final GlobalSearchScope scope = (GlobalSearchScope)searchScope;
+
+ final ContentIterator ci = new ContentIterator() {
+ public boolean processFile(final VirtualFile fileOrDir) {
+ if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) {
+ ++totalFilesToScan;
+ scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir, profile, ourPatternLanguage, ourPatternLanguage2));
+ }
+ return true;
+ }
+ };
+
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void 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;
+ final Language language = psiElement.getLanguage();
+
+ PsiFile file = psiElement instanceof PsiFile ? (PsiFile)psiElement : psiElement.getContainingFile();
+
+ if (profile.isMyFile(file, language, ourPatternLanguage, ourPatternLanguage2)) {
+ 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 MatchConstraintsSink(
+ sink,
+ options.getMaxMatchesCount(),
+ options.isDistinct(),
+ options.isCaseSensitiveMatch()
+ )
+ );
+ matchContext.setOptions(options);
+ matchContext.setMatcher(visitor);
+ visitor.setMatchContext(matchContext);
+
+ CompiledPattern compiledPattern = savedPattern;
+
+ if (compiledPattern == null) {
+
+ synchronized(getClass()) {
+ final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData);
+ if (data != null && options == data.lastOptions) {
+ compiledPattern = data.lastPattern;
+ }
+ lastMatchData = null;
+ }
+
+ if (compiledPattern==null) {
+ compiledPattern = ApplicationManager.getApplication().runReadAction(new Computable<CompiledPattern>() {
+ @Override
+ public CompiledPattern compute() {
+ return 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
+ * @param pattern to be searched
+ * @return list of matches found
+ * @throws MalformedPatternException
+ * @throws UnsupportedPatternException
+ */
+ protected List<MatchResult> testFindMatches(String source,
+ String pattern,
+ MatchOptions options,
+ boolean filePattern,
+ FileType sourceFileType,
+ String sourceExtension,
+ boolean physicalSourceFile)
+ throws MalformedPatternException, UnsupportedPatternException {
+
+ CollectingMatchResultSink sink = new CollectingMatchResultSink();
+
+ try {
+ PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source,
+ filePattern ? PatternTreeContext.File : PatternTreeContext.Block,
+ sourceFileType,
+ sourceExtension,
+ project, physicalSourceFile);
+
+ options.setSearchPattern(pattern);
+ options.setScope(new LocalSearchScope(elements));
+ testFindMatches(sink, options);
+ }
+ catch (IncorrectOperationException e) {
+ MalformedPatternException exception = new MalformedPatternException();
+ exception.initCause(e);
+ throw exception;
+ }
+
+ return sink.getMatches();
+ }
+
+ protected List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern) {
+ return testFindMatches(source, pattern, options, filePattern, options.getFileType(), null, false);
+ }
+
+ class TaskScheduler implements MatchingProcess {
+ private LinkedList<Runnable> tasks = new LinkedList<Runnable>();
+ private boolean ended;
+ private Runnable taskQueueEndAction;
+
+ private boolean suspended;
+
+ public void stop() {
+ ended = true;
+ }
+
+ public void pause() {
+ suspended = true;
+ }
+
+ public void resume() {
+ if (!suspended) return;
+ suspended = false;
+ executeNext();
+ }
+
+ public boolean isSuspended() {
+ return suspended;
+ }
+
+ 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.removeFirst();
+ try {
+ task.run();
+ }
+ catch (ProcessCanceledException e) {
+ ended = true;
+ clearSchedule();
+ throw e;
+ }
+ catch (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;
+ }
+
+ @Nullable
+ @Override
+ protected List<PsiElement> getPsiElementsToProcess() {
+ PsiElement file = this.file;
+ this.file = null;
+ return new SmartList<PsiElement>(file);
+ }
+ }
+
+ private abstract class MatchOneFile implements Runnable {
+ public void run() {
+ List<PsiElement> files = getPsiElementsToProcess();
+
+ if (progress!=null) {
+ progress.setFraction((double)scannedFilesCount/totalFilesToScan);
+ }
+
+ ++scannedFilesCount;
+
+ if (files == null || files.size() == 0) return;
+ final PsiFile psiFile = files.get(0).getContainingFile();
+
+ if (psiFile!=null) {
+ final Runnable action = new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ if (project.isDisposed()) return;
+ final PsiDocumentManager manager = PsiDocumentManager.getInstance(project);
+ Document document = manager.getDocument(psiFile);
+ if (document != null) manager.commitDocument(document);
+ }
+ });
+ }
+ };
+
+ if (ApplicationManager.getApplication().isDispatchThread()) {
+ action.run();
+ } else {
+ ApplicationManager.getApplication().invokeAndWait(
+ action,
+ ModalityState.defaultModalityState()
+ );
+ }
+ }
+
+ if (project.isDisposed()) return;
+
+ for(PsiElement file:files) {
+ if (file instanceof PsiFile) {
+ matchContext.getSink().processFile((PsiFile)file);
+ }
+
+ final PsiElement finalFile = file;
+ ApplicationManager.getApplication().runReadAction(
+ new Runnable() {
+ public void run() {
+ PsiElement file = finalFile;
+ if (!file.isValid()) return;
+ file = StructuralSearchUtil.getProfileByLanguage(file.getLanguage()).extendMatchOnePsiFile(file);
+ match(file);
+ }
+ }
+ );
+ }
+ }
+
+ protected abstract @Nullable List<PsiElement> getPsiElementsToProcess();
+ }
+
+ // Initiates the matching process for given element
+ // @param element the current search tree element
+ public void match(PsiElement element) {
+ MatchingStrategy strategy = matchContext.getPattern().getStrategy();
+
+ if (strategy.continueMatching(element)) {
+ visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element}));
+ return;
+ }
+ for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) {
+ match(el);
+ }
+ if (element instanceof PsiLanguageInjectionHost) {
+ InjectedLanguageUtil.enumerate(element, new PsiLanguageInjectionHost.InjectedPsiVisitor() {
+ @Override
+ public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) {
+ match(injectedPsi);
+ }
+ });
+ }
+ }
+
+ @Nullable
+ protected MatchResult isMatchedByDownUp(PsiElement element, final MatchOptions options) {
+ final CollectingMatchResultSink sink = new CollectingMatchResultSink();
+ CompiledPattern compiledPattern = prepareMatching(sink, options);
+
+ if (compiledPattern== null) {
+ assert false;
+ return null;
+ }
+
+ 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();
+
+ while (element.getClass() != targetNode.getClass()) {
+ element = element.getParent();
+ if (element == null) return null;
+ }
+
+ elementToStartMatching = element;
+ }
+ } else {
+ targetNode = StructuralSearchUtil.getProfileByPsiElement(element).extendMatchedByDownUp(targetNode);
+
+ MatchingHandler handler = null;
+
+ while (element.getClass() == targetNode.getClass() ||
+ compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element)
+ ) {
+ handler = compiledPattern.getHandler(targetNode);
+ handler.setPinnedElement(element);
+ elementToStartMatching = element;
+ if (handler instanceof TopLevelMatchingHandler) break;
+ element = element.getParent();
+ targetNode = targetNode.getParent();
+
+ if (options.isLooseMatching()) {
+ element = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(element);
+ targetNode = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(targetNode);
+ }
+ }
+
+ if (!(handler instanceof TopLevelMatchingHandler)) return null;
+ }
+
+ assert targetNode != null : "Could not match down up when no target node";
+
+ match(elementToStartMatching);
+ matchContext.getSink().matchingFinished();
+ final int matchCount = sink.getMatches().size();
+ assert matchCount <= 1;
+ return matchCount > 0 ? sink.getMatches().get(0) : null;
+ }
+
+ private class MatchOneVirtualFile extends MatchOneFile {
+ private final VirtualFile myFileOrDir;
+ private final StructuralSearchProfile myProfile;
+ private final Language myOurPatternLanguage;
+ private final Language myOurPatternLanguage2;
+
+ public MatchOneVirtualFile(VirtualFile fileOrDir,
+ StructuralSearchProfile profile,
+ Language ourPatternLanguage,
+ Language ourPatternLanguage2) {
+ myFileOrDir = fileOrDir;
+ myProfile = profile;
+ myOurPatternLanguage = ourPatternLanguage;
+ myOurPatternLanguage2 = ourPatternLanguage2;
+ }
+
+ @Nullable
+ @Override
+ protected List<PsiElement> getPsiElementsToProcess() {
+ return ApplicationManager.getApplication().runReadAction(new Computable<List<PsiElement>>() {
+ @Override
+ public List<PsiElement> compute() {
+ PsiFile file = PsiManager.getInstance(project).findFile(myFileOrDir);
+ if (file == null) {
+ return null;
+ }
+
+ final FileViewProvider viewProvider = file.getViewProvider();
+ List<PsiElement> elementsToProcess = new SmartList<PsiElement>();
+
+ for(Language lang: viewProvider.getLanguages()) {
+ if (myProfile.isMyFile(file, lang, myOurPatternLanguage, myOurPatternLanguage2)) {
+ elementsToProcess.add(viewProvider.getPsi(lang));
+ }
+ }
+
+ return elementsToProcess;
+ }
+ });
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java
new file mode 100644
index 000000000000..ffc9ae78e876
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java
@@ -0,0 +1,67 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.lang.Language;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
+import com.intellij.util.IncorrectOperationException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Mar 19, 2004
+ * Time: 6:56:25 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class MatcherImplUtil {
+
+ public static void transform(MatchOptions options) {
+ if (options.hasVariableConstraints()) return;
+ PatternCompiler.transformOldPattern(options);
+ }
+
+ public static PsiElement[] createTreeFromText(String text, PatternTreeContext context, FileType fileType, Project project)
+ throws IncorrectOperationException {
+ return createTreeFromText(text, context, fileType, null, null, project, false);
+ }
+
+ public static PsiElement[] createSourceTreeFromText(String text,
+ PatternTreeContext context,
+ FileType fileType,
+ String extension,
+ Project project,
+ boolean physical) {
+ if (fileType instanceof LanguageFileType) {
+ Language language = ((LanguageFileType)fileType).getLanguage();
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ if (profile != null) {
+ return profile.createPatternTree(text, context, fileType, null, null, extension, project, physical);
+ }
+ }
+ return PsiElement.EMPTY_ARRAY;
+ }
+
+ public static PsiElement[] createTreeFromText(String text,
+ PatternTreeContext context,
+ FileType fileType,
+ Language language,
+ String contextName,
+ Project project,
+ boolean physical) throws IncorrectOperationException {
+ if (language == null && fileType instanceof LanguageFileType) {
+ language = ((LanguageFileType)fileType).getLanguage();
+ }
+ if (language != null) {
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ if (profile != null) {
+ return profile.createPatternTree(text, context, fileType, language, contextName, null, project, physical);
+ }
+ }
+ return PsiElement.EMPTY_ARRAY;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java
new file mode 100644
index 000000000000..7d93c9ae82e0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java
@@ -0,0 +1,8 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public enum PatternTreeContext {
+ File, Block, Class
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java
new file mode 100644
index 000000000000..0c9f4945e3a5
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java
@@ -0,0 +1,22 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlCompiledPattern extends CompiledPattern {
+ private static final String XML_TYPED_VAR_PREFIX = "__";
+
+ public XmlCompiledPattern() {
+ setStrategy(XmlMatchingStrategy.getInstance());
+ }
+
+ public String[] getTypedVarPrefixes() {
+ return new String[] {XML_TYPED_VAR_PREFIX};
+ }
+
+ public boolean isTypedVar(final String str) {
+ return str.startsWith(XML_TYPED_VAR_PREFIX);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java
new file mode 100644
index 000000000000..d2e1e210534b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java
@@ -0,0 +1,130 @@
+package com.intellij.structuralsearch.impl.matcher;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiWhiteSpace;
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.psi.xml.*;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlMatchingVisitor extends XmlElementVisitor {
+ private final GlobalMatchingVisitor myMatchingVisitor;
+ private final boolean myCaseSensitive;
+
+ public XmlMatchingVisitor(GlobalMatchingVisitor matchingVisitor) {
+ myMatchingVisitor = matchingVisitor;
+ myCaseSensitive = myMatchingVisitor.getMatchContext().getOptions().isCaseSensitiveMatch();
+ }
+
+ @Override
+ public void visitElement(final PsiElement element) {
+ myMatchingVisitor.setResult(element.textMatches(element));
+ }
+
+ @Override public void visitXmlAttribute(XmlAttribute attribute) {
+ final XmlAttribute another = (XmlAttribute)myMatchingVisitor.getElement();
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(attribute.getName());
+
+ myMatchingVisitor.setResult(matches(attribute.getName(), another.getName()) || isTypedVar);
+ if (myMatchingVisitor.getResult()) {
+ myMatchingVisitor.setResult(myMatchingVisitor.match(attribute.getValueElement(), another.getValueElement()));
+ }
+
+ if (myMatchingVisitor.getResult() && isTypedVar) {
+ MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(attribute.getName());
+ myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext()));
+ }
+ }
+
+ @Override public void visitXmlAttributeValue(XmlAttributeValue value) {
+ final XmlAttributeValue another = (XmlAttributeValue)myMatchingVisitor.getElement();
+ final String text = value.getValue();
+
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text);
+ MatchingHandler handler;
+
+ if (isTypedVar && (handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( text )) instanceof SubstitutionHandler) {
+ String text2 = another.getText();
+ int offset = text2.length() > 0 && ( text2.charAt(0) == '"' || text2.charAt(0) == '\'') ? 1:0;
+ myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, offset, text2.length() - offset,
+ myMatchingVisitor.getMatchContext()));
+ } else {
+ myMatchingVisitor.setResult(matches(text, another.getValue()));
+ }
+ }
+
+ @Override public void visitXmlTag(XmlTag tag) {
+ final XmlTag another = (XmlTag)myMatchingVisitor.getElement();
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(tag.getName());
+
+ myMatchingVisitor.setResult((matches(tag.getName(), another.getName()) || isTypedVar) &&
+ myMatchingVisitor.matchInAnyOrder(tag.getAttributes(), another.getAttributes()));
+
+ if(myMatchingVisitor.getResult()) {
+ final XmlTagChild[] contentChildren = tag.getValue().getChildren();
+
+ if (contentChildren.length > 0) {
+ PsiElement[] patternNodes = contentChildren;
+ PsiElement[] matchedNodes = another.getValue().getChildren();
+
+ if (contentChildren.length != 1) {
+ patternNodes = expandXmlTexts(patternNodes);
+ matchedNodes = expandXmlTexts(matchedNodes);
+ }
+
+ myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(
+ new ArrayBackedNodeIterator(patternNodes),
+ new ArrayBackedNodeIterator(matchedNodes)
+ ));
+ }
+ }
+
+ if (myMatchingVisitor.getResult() && isTypedVar) {
+ MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( tag.getName() );
+ myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext()));
+ }
+ }
+
+ private static PsiElement[] expandXmlTexts(PsiElement[] children) {
+ List<PsiElement> result = new ArrayList<PsiElement>(children.length);
+ for(PsiElement c:children) {
+ if (c instanceof XmlText) {
+ for(PsiElement p:c.getChildren()) {
+ if (!(p instanceof PsiWhiteSpace)) result.add(p);
+ }
+ } else if (!(c instanceof PsiWhiteSpace)) {
+ result.add(c);
+ }
+ }
+ return PsiUtilCore.toPsiElementArray(result);
+ }
+
+ @Override public void visitXmlText(XmlText text) {
+ myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(text.getFirstChild(), myMatchingVisitor.getElement().getFirstChild()));
+ }
+
+ @Override public void visitXmlToken(XmlToken token) {
+ if (token.getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) {
+ String text = token.getText();
+ final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text);
+
+ if (isTypedVar) {
+ myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(token, myMatchingVisitor.getElement()));
+ } else {
+ myMatchingVisitor.setResult(matches(text, myMatchingVisitor.getElement().getText()));
+ }
+ }
+ }
+
+ private boolean matches(String a, String b) {
+ return myCaseSensitive ? a.equals(b) : a.equalsIgnoreCase(b);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java
new file mode 100644
index 000000000000..09758a5e2191
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java
@@ -0,0 +1,71 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 17.11.2004
+ * Time: 19:26:37
+ * To change this template use File | Settings | File Templates.
+ */
+public class CompileContext {
+ private OptimizingSearchHelper searchHelper;
+
+ private CompiledPattern pattern;
+ private MatchOptions options;
+ private Project project;
+
+ public void clear() {
+ if (searchHelper!=null) searchHelper.clear();
+
+ project = null;
+ pattern = null;
+ options = null;
+ }
+
+ public void init(final CompiledPattern _result, final MatchOptions _options, final Project _project, final boolean _findMatchingFiles) {
+ options = _options;
+ project = _project;
+ pattern = _result;
+
+ searchHelper = ApplicationManager.getApplication().isUnitTestMode() ?
+ new TestModeOptimizingSearchHelper(this) :
+ new FindInFilesOptimizingSearchHelper(this, _findMatchingFiles, _project);
+ }
+
+ public OptimizingSearchHelper getSearchHelper() {
+ return searchHelper;
+ }
+
+ void setSearchHelper(OptimizingSearchHelper searchHelper) {
+ this.searchHelper = searchHelper;
+ }
+
+ public CompiledPattern getPattern() {
+ return pattern;
+ }
+
+ void setPattern(CompiledPattern pattern) {
+ this.pattern = pattern;
+ }
+
+ MatchOptions getOptions() {
+ return options;
+ }
+
+ void setOptions(MatchOptions options) {
+ this.options = options;
+ }
+
+ Project getProject() {
+ return project;
+ }
+
+ void setProject(Project project) {
+ this.project = project;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java
new file mode 100644
index 000000000000..4b5ad7c64545
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java
@@ -0,0 +1,59 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiElement;
+
+import java.util.ArrayList;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 17.11.2004
+ * Time: 19:24:40
+ * To change this template use File | Settings | File Templates.
+ */
+class DeleteNodesAction implements Runnable {
+ private final ArrayList elements;
+
+ DeleteNodesAction(ArrayList _elements) {
+ elements = _elements;
+ }
+
+ private void delete(PsiElement first, PsiElement last) throws Exception {
+ if (last==first) {
+ first.delete();
+ } else {
+ first.getParent().deleteChildRange(first,last);
+ }
+ }
+ public void run() {
+ try {
+ PsiElement first= null;
+ PsiElement last = null;
+
+ for(int i = 0;i < elements.size(); ++i) {
+ final PsiElement el = (PsiElement)elements.get(i);
+
+ if (!el.isValid()) continue;
+
+ if (first==null) {
+ first = last = el;
+ } else if (last.getNextSibling()==el) {
+ last = el;
+ } else {
+ delete(first,last);
+ first = last = null;
+ --i;
+ continue;
+ }
+ }
+
+ if (first!=null) {
+ delete(first,last);
+ }
+ } catch(Throwable ex) {
+ ex.printStackTrace();
+ } finally {
+ elements.clear();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java
new file mode 100644
index 000000000000..ff7f06a36d5b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java
@@ -0,0 +1,103 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.lang.Language;
+import com.intellij.lang.LanguageNamesValidation;
+import com.intellij.lang.refactoring.NamesValidator;
+import com.intellij.lexer.Lexer;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.PsiSearchHelper;
+import com.intellij.util.Processor;
+import gnu.trove.THashMap;
+
+import java.util.Set;
+
+/**
+ * @author Maxim.Mossienko
+*/
+class FindInFilesOptimizingSearchHelper extends OptimizingSearchHelperBase {
+ private PsiSearchHelper helper;
+ private THashMap<PsiFile,PsiFile> filesToScan;
+ private THashMap<PsiFile,PsiFile> filesToScan2;
+ private Lexer javaLexer;
+
+ private final boolean findMatchingFiles;
+
+ FindInFilesOptimizingSearchHelper(CompileContext _context, boolean _findMatchngFiles, Project project) {
+ super(_context);
+ findMatchingFiles = _findMatchngFiles;
+
+ if (findMatchingFiles) {
+ helper = PsiSearchHelper.SERVICE.getInstance(project);
+
+ if (filesToScan == null) {
+ filesToScan = new THashMap<PsiFile,PsiFile>();
+ filesToScan2 = new THashMap<PsiFile,PsiFile>();
+ }
+ }
+ }
+
+ public boolean doOptimizing() {
+ return findMatchingFiles;
+ }
+
+ public void clear() {
+ super.clear();
+
+ if (filesToScan != null) {
+ filesToScan.clear();
+ filesToScan2.clear();
+
+ helper = null;
+ }
+ }
+
+ protected void doAddSearchWordInCode(final String refname) {
+ final FileType fileType = context.getOptions().getFileType();
+ final Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() : Language.ANY;
+ final NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(language);
+ if (namesValidator.isKeyword(refname, context.getProject())) {
+ helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true);
+ } else {
+ helper.processAllFilesWithWord(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true);
+ }
+ }
+
+ protected void doAddSearchWordInText(final String refname) {
+ helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true);
+ }
+
+ protected void doAddSearchWordInComments(final String refname) {
+ helper.processAllFilesWithWordInComments(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor());
+ }
+
+ protected void doAddSearchWordInLiterals(final String refname) {
+ helper.processAllFilesWithWordInLiterals(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor());
+ }
+
+ public void endTransaction() {
+ super.endTransaction();
+ THashMap<PsiFile,PsiFile> map = filesToScan;
+ if (map.size() > 0) map.clear();
+ filesToScan = filesToScan2;
+ filesToScan2 = map;
+ }
+
+ public Set<PsiFile> getFilesSetToScan() {
+ return filesToScan.keySet();
+ }
+
+ private class MyFileProcessor implements Processor<PsiFile> {
+ public boolean process(PsiFile file) {
+ if (scanRequest == 0 ||
+ filesToScan.get(file)!=null) {
+ filesToScan2.put(file,file);
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java
new file mode 100644
index 000000000000..130751adec67
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java
@@ -0,0 +1,314 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.filters.CompositeFilter;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.LiteralWithSubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.intellij.structuralsearch.MatchOptions.INSTANCE_MODIFIER_NAME;
+import static com.intellij.structuralsearch.MatchOptions.MODIFIER_ANNOTATION_NAME;
+
+/**
+ * @author maxim
+ */
+public class GlobalCompilingVisitor {
+ @NonNls private static final String SUBSTITUTION_PATTERN_STR = "\\b(__\\$_\\w+)\\b";
+ private static final Pattern ourSubstitutionPattern = Pattern.compile(SUBSTITUTION_PATTERN_STR);
+ private static final Set<String> ourReservedWords = new HashSet<String>(Arrays.asList(MODIFIER_ANNOTATION_NAME, INSTANCE_MODIFIER_NAME)) {
+ {
+ for (StructuralSearchProfile profile : Extensions.getExtensions(StructuralSearchProfile.EP_NAME)) {
+ addAll(profile.getReservedWords());
+ }
+ }
+ };
+ private static final Pattern ourAlternativePattern = Pattern.compile("^\\((.+)\\)$");
+ @NonNls private static final String WORD_SEARCH_PATTERN_STR = ".*?\\b(.+?)\\b.*?";
+ private static final Pattern ourWordSearchPattern = Pattern.compile(WORD_SEARCH_PATTERN_STR);
+ private CompileContext context;
+ private final ArrayList<PsiElement> myLexicalNodes = new ArrayList<PsiElement>();
+
+ private int myCodeBlockLevel;
+
+ private static final NodeFilter ourFilter = LexicalNodesFilter.getInstance();
+
+ public static NodeFilter getFilter() {
+ return ourFilter;
+ }
+
+ public void setHandler(PsiElement element, MatchingHandler handler) {
+ MatchingHandler realHandler = context.getPattern().getHandlerSimple(element);
+
+ if (realHandler instanceof SubstitutionHandler) {
+ ((SubstitutionHandler)realHandler).setMatchHandler(handler);
+ }
+ else {
+ // @todo care about composite handler in this case of simple handler!
+ context.getPattern().setHandler(element, handler);
+ }
+ }
+
+ public final void handle(PsiElement element) {
+
+ if ((!ourFilter.accepts(element) ||
+ StructuralSearchUtil.isIdentifier(element)) &&
+ context.getPattern().isRealTypedVar(element) &&
+ context.getPattern().getHandlerSimple(element) == null
+ ) {
+ String name = context.getPattern().getTypedVarString(element);
+ // name is the same for named element (clazz,methods, etc) and token (name of ... itself)
+ // @todo need fix this
+ final SubstitutionHandler handler;
+
+ context.getPattern().setHandler(
+ element,
+ handler = (SubstitutionHandler)context.getPattern().getHandler(name)
+ );
+
+ if (handler != null && context.getOptions().getVariableConstraint(handler.getName()).isPartOfSearchResults()) {
+ handler.setTarget(true);
+ context.getPattern().setTargetNode(element);
+ }
+ }
+ }
+
+ public CompileContext getContext() {
+ return context;
+ }
+
+ public int getCodeBlockLevel() {
+ return myCodeBlockLevel;
+ }
+
+ public void setCodeBlockLevel(int codeBlockLevel) {
+ this.myCodeBlockLevel = codeBlockLevel;
+ }
+
+ static void setFilter(MatchingHandler handler, NodeFilter filter) {
+ if (handler.getFilter() != null &&
+ handler.getFilter().getClass() != filter.getClass()
+ ) {
+ // for constructor we will have the same handler for class and method and tokens itselfa
+ handler.setFilter(
+ new CompositeFilter(
+ filter,
+ handler.getFilter()
+ )
+ );
+ }
+ else {
+ handler.setFilter(filter);
+ }
+ }
+
+ public ArrayList<PsiElement> getLexicalNodes() {
+ return myLexicalNodes;
+ }
+
+ public void addLexicalNode(PsiElement node) {
+ myLexicalNodes.add(node);
+ }
+
+ void compile(PsiElement[] elements, CompileContext context) {
+ myCodeBlockLevel = 0;
+ this.context = context;
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType());
+ assert profile != null;
+ profile.compile(elements, this);
+
+ if (context.getPattern().getStrategy() == null) {
+ System.out.println();
+ }
+ }
+
+ @Nullable
+ public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind) {
+ return processPatternStringWithFragments(pattern, kind, ourSubstitutionPattern);
+ }
+
+ @Nullable
+ public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind, Pattern substitutionPattern) {
+ String content;
+
+ if (kind == OccurenceKind.LITERAL) {
+ content = pattern.substring(1, pattern.length() - 1);
+ }
+ else if (kind == OccurenceKind.COMMENT) {
+ content = pattern;
+ }
+ else {
+ return null;
+ }
+
+ @NonNls StringBuilder buf = new StringBuilder(content.length());
+ Matcher matcher = substitutionPattern.matcher(content);
+ List<SubstitutionHandler> handlers = null;
+ int start = 0;
+ String word;
+ boolean hasLiteralContent = false;
+
+ SubstitutionHandler handler = null;
+ while (matcher.find()) {
+ if (handlers == null) handlers = new ArrayList<SubstitutionHandler>();
+ handler = (SubstitutionHandler)getContext().getPattern().getHandler(matcher.group(1));
+ if (handler != null) handlers.add(handler);
+
+ word = content.substring(start, matcher.start());
+
+ if (word.length() > 0) {
+ buf.append(StructuralSearchUtil.shieldSpecialChars(word));
+ hasLiteralContent = true;
+
+ processTokenizedName(word, false, kind);
+ }
+
+ RegExpPredicate predicate = MatchingHandler.getSimpleRegExpPredicate(handler);
+
+ if (predicate == null || !predicate.isWholeWords()) {
+ buf.append("(.*?)");
+ }
+ else {
+ buf.append(".*?\\b(").append(predicate.getRegExp()).append(")\\b.*?");
+ }
+
+ if (isSuitablePredicate(predicate, handler)) {
+ processTokenizedName(predicate.getRegExp(), false, kind);
+ }
+
+ start = matcher.end();
+ }
+
+ word = content.substring(start, content.length());
+
+ if (word.length() > 0) {
+ hasLiteralContent = true;
+ buf.append(StructuralSearchUtil.shieldSpecialChars(word));
+
+ processTokenizedName(word, false, kind);
+ }
+
+ if (hasLiteralContent) {
+ if (kind == OccurenceKind.LITERAL) {
+ buf.insert(0, "[\"']");
+ buf.append("[\"']");
+ }
+ buf.append("$");
+ }
+
+ if (handlers != null) {
+ return hasLiteralContent ? new LiteralWithSubstitutionHandler(buf.toString(), handlers) : handler;
+ }
+
+ return null;
+ }
+
+ @Contract("null,_ -> false")
+ static boolean isSuitablePredicate(RegExpPredicate predicate, SubstitutionHandler handler) {
+ return predicate != null && handler.getMinOccurs() != 0 && predicate.couldBeOptimized();
+ }
+
+ public static void addFilesToSearchForGivenWord(String refname,
+ boolean endTransaction,
+ GlobalCompilingVisitor.OccurenceKind kind,
+ CompileContext compileContext) {
+ if (!compileContext.getSearchHelper().doOptimizing()) {
+ return;
+ }
+ if (ourReservedWords.contains(refname)) return; // skip our special annotations !!!
+
+ boolean addedSomething = false;
+
+ if (kind == GlobalCompilingVisitor.OccurenceKind.CODE) {
+ addedSomething = compileContext.getSearchHelper().addWordToSearchInCode(refname);
+ }
+ else if (kind == GlobalCompilingVisitor.OccurenceKind.COMMENT) {
+ addedSomething = compileContext.getSearchHelper().addWordToSearchInComments(refname);
+ }
+ else if (kind == GlobalCompilingVisitor.OccurenceKind.LITERAL) {
+ addedSomething = compileContext.getSearchHelper().addWordToSearchInLiterals(refname);
+ }
+
+ if (addedSomething && endTransaction) {
+ compileContext.getSearchHelper().endTransaction();
+ }
+ }
+
+ public void processTokenizedName(String name, boolean skipComments, GlobalCompilingVisitor.OccurenceKind kind) {
+ WordTokenizer tokenizer = new WordTokenizer(name);
+ for (Iterator<String> i = tokenizer.iterator(); i.hasNext();) {
+ String nextToken = i.next();
+ if (skipComments &&
+ (nextToken.equals("/*") || nextToken.equals("/**") || nextToken.equals("*/") || nextToken.equals("*") || nextToken.equals("//"))
+ ) {
+ continue;
+ }
+
+ Matcher matcher = ourAlternativePattern.matcher(nextToken);
+ if (matcher.matches()) {
+ StringTokenizer alternatives = new StringTokenizer(matcher.group(1), "|");
+ while (alternatives.hasMoreTokens()) {
+ addFilesToSearchForGivenWord(alternatives.nextToken(), !alternatives.hasMoreTokens(), kind, getContext());
+ }
+ }
+ else {
+ addFilesToSearchForGivenWord(nextToken, true, kind, getContext());
+ }
+ }
+ }
+
+ public enum OccurenceKind {
+ LITERAL, COMMENT, CODE
+ }
+
+ private static class WordTokenizer {
+ private final List<String> myWords = new ArrayList<String>();
+
+ WordTokenizer(String text) {
+ final StringTokenizer tokenizer = new StringTokenizer(text);
+ Matcher matcher = null;
+
+ while (tokenizer.hasMoreTokens()) {
+ String nextToken = tokenizer.nextToken();
+ if (matcher == null) {
+ matcher = ourWordSearchPattern.matcher(nextToken);
+ }
+ else {
+ matcher.reset(nextToken);
+ }
+
+ nextToken = (matcher.matches()) ? matcher.group(1) : nextToken;
+ int lastWordStart = 0;
+ int i;
+ for (i = 0; i < nextToken.length(); ++i) {
+ if (!Character.isJavaIdentifierStart(nextToken.charAt(i))) {
+ if (i != lastWordStart) {
+ myWords.add(nextToken.substring(lastWordStart, i));
+ }
+ lastWordStart = i + 1;
+ }
+ }
+
+ if (i != lastWordStart) {
+ myWords.add(nextToken.substring(lastWordStart, i));
+ }
+ }
+ }
+
+ Iterator<String> iterator() {
+ return myWords.iterator();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java
new file mode 100644
index 000000000000..8d6a08b6ef95
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java
@@ -0,0 +1,27 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiFile;
+
+import java.util.Set;
+
+/**
+ * @author Maxim.Mossienko
+*/
+public interface OptimizingSearchHelper {
+ boolean doOptimizing();
+ void clear();
+
+ boolean addWordToSearchInCode(final String refname);
+
+ boolean addWordToSearchInText(final String refname);
+
+ boolean addWordToSearchInComments(final String refname);
+
+ boolean addWordToSearchInLiterals(final String refname);
+
+ void endTransaction();
+
+ boolean isScannedSomething();
+
+ Set<PsiFile> getFilesSetToScan();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java
new file mode 100644
index 000000000000..2d6f2bc41b37
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java
@@ -0,0 +1,85 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import gnu.trove.THashSet;
+
+/**
+ * @author Maxim.Mossienko
+*/
+abstract class OptimizingSearchHelperBase implements OptimizingSearchHelper {
+ private final THashSet<String> scanned;
+ private final THashSet<String> scannedText;
+ private final THashSet<String> scannedComments;
+ private final THashSet<String> scannedLiterals;
+ protected int scanRequest;
+
+
+ protected final CompileContext context;
+
+ OptimizingSearchHelperBase(CompileContext _context) {
+ context = _context;
+
+ scanRequest = 0;
+ scanned = new THashSet<String>();
+ scannedText = new THashSet<String>();
+ scannedComments = new THashSet<String>();
+ scannedLiterals = new THashSet<String>();
+ }
+
+ public void clear() {
+ scanned.clear();
+ scannedComments.clear();
+ scannedLiterals.clear();
+ }
+
+ public boolean addWordToSearchInCode(final String refname) {
+ if (!scanned.contains(refname)) {
+ doAddSearchWordInCode(refname);
+ scanned.add( refname );
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean addWordToSearchInText(final String refname) {
+ if (!scannedText.contains(refname)) {
+ doAddSearchWordInText(refname);
+ scannedText.add(refname);
+ return true;
+ }
+ return false;
+ }
+
+ protected abstract void doAddSearchWordInCode(final String refname);
+ protected abstract void doAddSearchWordInText(final String refname);
+
+ protected abstract void doAddSearchWordInComments(final String refname);
+ protected abstract void doAddSearchWordInLiterals(final String refname);
+
+ public void endTransaction() {
+ scanRequest++;
+ }
+
+ public boolean addWordToSearchInComments(final String refname) {
+ if (!scannedComments.contains(refname)) {
+ doAddSearchWordInComments(refname);
+
+ scannedComments.add( refname );
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addWordToSearchInLiterals(final String refname) {
+ if (!scannedLiterals.contains(refname)) {
+ doAddSearchWordInLiterals(refname);
+ scannedLiterals.add( refname );
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isScannedSomething() {
+ return scanned.size() > 0 || scannedComments.size() > 0 || scannedLiterals.size() > 0;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java
new file mode 100644
index 000000000000..e290ebb70793
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java
@@ -0,0 +1,512 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.codeInsight.template.Template;
+import com.intellij.codeInsight.template.TemplateManager;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.lang.Language;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.LanguageFileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiErrorElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiRecursiveElementWalkingVisitor;
+import com.intellij.psi.impl.source.PsiFileImpl;
+import com.intellij.psi.impl.source.tree.LeafElement;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.LocalSearchScope;
+import com.intellij.psi.util.PsiUtilCore;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.MatchPredicateProvider;
+import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil;
+import com.intellij.structuralsearch.impl.matcher.PatternTreeContext;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.predicates.*;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.util.IncorrectOperationException;
+import gnu.trove.TIntArrayList;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Compiles the handlers for usability
+ */
+public class PatternCompiler {
+ private static CompileContext lastTestingContext;
+
+ public static void transformOldPattern(MatchOptions options) {
+ StringToConstraintsTransformer.transformOldPattern(options);
+ }
+
+ public static CompiledPattern compilePattern(final Project project, final MatchOptions options) throws MalformedPatternException,
+ UnsupportedOperationException {
+ FileType fileType = options.getFileType();
+ assert fileType instanceof LanguageFileType;
+ Language language = ((LanguageFileType)fileType).getLanguage();
+ StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
+ assert profile != null;
+ CompiledPattern result = profile.createCompiledPattern();
+
+ final String[] prefixes = result.getTypedVarPrefixes();
+ assert prefixes.length > 0;
+
+ final CompileContext context = new CompileContext();
+ if (ApplicationManager.getApplication().isUnitTestMode()) lastTestingContext = context;
+
+ /*CompiledPattern result = options.getFileType() == StdFileTypes.JAVA ?
+ new JavaCompiledPattern() :
+ new XmlCompiledPattern();*/
+
+ try {
+ context.init(result, options, project, options.getScope() instanceof GlobalSearchScope);
+
+ List<PsiElement> elements = compileByAllPrefixes(project, options, result, context, prefixes);
+
+ context.getPattern().setNodes(elements);
+
+ if (context.getSearchHelper().doOptimizing() && context.getSearchHelper().isScannedSomething()) {
+ final Set<PsiFile> set = context.getSearchHelper().getFilesSetToScan();
+ final List<PsiFile> filesToScan = new ArrayList<PsiFile>(set.size());
+ final GlobalSearchScope scope = (GlobalSearchScope)options.getScope();
+
+ for (final PsiFile file : set) {
+ if (!scope.contains(file.getVirtualFile())) {
+ continue;
+ }
+
+ if (file instanceof PsiFileImpl) {
+ ((PsiFileImpl)file).clearCaches();
+ }
+ filesToScan.add(file);
+ }
+
+ if (filesToScan.size() == 0) {
+ throw new MalformedPatternException(SSRBundle.message("ssr.will.not.find.anything"));
+ }
+ result.setScope(
+ new LocalSearchScope(PsiUtilCore.toPsiElementArray(filesToScan))
+ );
+ }
+ } finally {
+ context.clear();
+ }
+
+ return result;
+ }
+
+ public static String getLastFindPlan() {
+ return ((TestModeOptimizingSearchHelper)lastTestingContext.getSearchHelper()).getSearchPlan();
+ }
+
+ @NotNull
+ private static List<PsiElement> compileByAllPrefixes(Project project,
+ MatchOptions options,
+ CompiledPattern pattern,
+ CompileContext context,
+ String[] applicablePrefixes) {
+ if (applicablePrefixes.length == 0) {
+ return Collections.emptyList();
+ }
+
+ List<PsiElement> elements = doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context);
+ if (elements.isEmpty()) {
+ return elements;
+ }
+
+ final PsiFile file = elements.get(0).getContainingFile();
+ if (file == null) {
+ return elements;
+ }
+
+ final PsiElement last = elements.get(elements.size() - 1);
+ final Pattern[] patterns = new Pattern[applicablePrefixes.length];
+
+ for (int i = 0; i < applicablePrefixes.length; i++) {
+ String s = StructuralSearchUtil.shieldSpecialChars(applicablePrefixes[i]);
+ patterns[i] = Pattern.compile(s + "\\w+\\b");
+ }
+
+ final int[] varEndOffsets = findAllTypedVarOffsets(file, patterns);
+
+ final int patternEndOffset = last.getTextRange().getEndOffset();
+ if (elements.size() == 0 ||
+ checkErrorElements(file, patternEndOffset, patternEndOffset, varEndOffsets, true) != Boolean.TRUE) {
+ return elements;
+ }
+
+ final int varCount = varEndOffsets.length;
+ final String[] prefixSequence = new String[varCount];
+
+ for (int i = 0; i < varCount; i++) {
+ prefixSequence[i] = applicablePrefixes[0];
+ }
+
+ final List<PsiElement> finalElements =
+ compileByPrefixes(project, options, pattern, context, applicablePrefixes, patterns, prefixSequence, 0);
+ return finalElements != null
+ ? finalElements
+ : doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context);
+ }
+
+ @Nullable
+ private static List<PsiElement> compileByPrefixes(Project project,
+ MatchOptions options,
+ CompiledPattern pattern,
+ CompileContext context,
+ String[] applicablePrefixes,
+ Pattern[] substitutionPatterns,
+ String[] prefixSequence,
+ int index) {
+ if (index >= prefixSequence.length) {
+ final List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context);
+ if (elements.isEmpty()) {
+ return elements;
+ }
+
+ final PsiElement parent = elements.get(0).getParent();
+ final PsiElement last = elements.get(elements.size() - 1);
+ final int[] varEndOffsets = findAllTypedVarOffsets(parent.getContainingFile(), substitutionPatterns);
+ final int patternEndOffset = last.getTextRange().getEndOffset();
+ return checkErrorElements(parent, patternEndOffset, patternEndOffset, varEndOffsets, false) != Boolean.TRUE
+ ? elements
+ : null;
+ }
+
+ String[] alternativeVariant = null;
+
+ for (String applicablePrefix : applicablePrefixes) {
+ prefixSequence[index] = applicablePrefix;
+
+ List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context);
+ if (elements.isEmpty()) {
+ return elements;
+ }
+
+ final PsiFile file = elements.get(0).getContainingFile();
+ if (file == null) {
+ return elements;
+ }
+
+ final int[] varEndOffsets = findAllTypedVarOffsets(file, substitutionPatterns);
+ final int offset = varEndOffsets[index];
+
+ final int patternEndOffset = elements.get(elements.size() - 1).getTextRange().getEndOffset();
+ final Boolean result = checkErrorElements(file, offset, patternEndOffset, varEndOffsets, false);
+
+ if (result == Boolean.TRUE) {
+ continue;
+ }
+
+ if (result == Boolean.FALSE || (result == null && alternativeVariant == null)) {
+ final List<PsiElement> finalElements =
+ compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, prefixSequence, index + 1);
+ if (finalElements != null) {
+ if (result == Boolean.FALSE) {
+ return finalElements;
+ }
+ alternativeVariant = new String[prefixSequence.length];
+ System.arraycopy(prefixSequence, 0, alternativeVariant, 0, prefixSequence.length);
+ }
+ }
+ }
+
+ return alternativeVariant != null ?
+ compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, alternativeVariant, index + 1) :
+ null;
+ }
+
+ @NotNull
+ private static int[] findAllTypedVarOffsets(final PsiFile file, final Pattern[] substitutionPatterns) {
+ final TIntHashSet result = new TIntHashSet();
+
+ file.accept(new PsiRecursiveElementWalkingVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ if (element instanceof LeafElement) {
+ final String text = element.getText();
+
+ for (Pattern pattern : substitutionPatterns) {
+ final Matcher matcher = pattern.matcher(text);
+
+ while (matcher.find()) {
+ result.add(element.getTextRange().getStartOffset() + matcher.end());
+ }
+ }
+ }
+ }
+ });
+
+ final int[] resultArray = result.toArray();
+ Arrays.sort(resultArray);
+ return resultArray;
+ }
+
+
+ /**
+ * False: there are no error elements before offset, except patternEndOffset
+ * Null: there are only error elements located exactly after template variables or at the end of the pattern
+ * True: otherwise
+ */
+ @Nullable
+ private static Boolean checkErrorElements(PsiElement element,
+ final int offset,
+ final int patternEndOffset,
+ final int[] varEndOffsets,
+ final boolean strict) {
+ final TIntArrayList errorOffsets = new TIntArrayList();
+ final boolean[] containsErrorTail = {false};
+ final TIntHashSet varEndOffsetsSet = new TIntHashSet(varEndOffsets);
+
+ element.accept(new PsiRecursiveElementWalkingVisitor() {
+ @Override
+ public void visitElement(PsiElement element) {
+ super.visitElement(element);
+
+ if (!(element instanceof PsiErrorElement)) {
+ return;
+ }
+
+ final int startOffset = element.getTextRange().getStartOffset();
+
+ if ((strict || !varEndOffsetsSet.contains(startOffset)) && startOffset != patternEndOffset) {
+ errorOffsets.add(startOffset);
+ }
+
+ if (startOffset == offset) {
+ containsErrorTail[0] = true;
+ }
+ }
+ });
+
+ for (int i = 0; i < errorOffsets.size(); i++) {
+ final int errorOffset = errorOffsets.get(i);
+ if (errorOffset <= offset) {
+ return true;
+ }
+ }
+ return containsErrorTail[0] ? null : false;
+ }
+
+ private interface PrefixProvider {
+ String getPrefix(int varIndex);
+ }
+
+ private static class ConstantPrefixProvider implements PrefixProvider {
+ private final String myPrefix;
+
+ private ConstantPrefixProvider(String prefix) {
+ myPrefix = prefix;
+ }
+
+ @Override
+ public String getPrefix(int varIndex) {
+ return myPrefix;
+ }
+ }
+
+ private static class ArrayPrefixProvider implements PrefixProvider {
+ private final String[] myPrefixes;
+
+ private ArrayPrefixProvider(String[] prefixes) {
+ myPrefixes = prefixes;
+ }
+
+ @Override
+ public String getPrefix(int varIndex) {
+ try {
+ return myPrefixes[varIndex];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+ }
+
+ private static List<PsiElement> doCompile(Project project,
+ MatchOptions options,
+ CompiledPattern result,
+ PrefixProvider prefixProvider,
+ CompileContext context) {
+ result.clearHandlers();
+ context.init(result, options, project, options.getScope() instanceof GlobalSearchScope);
+
+ final StringBuilder buf = new StringBuilder();
+
+ Template template = TemplateManager.getInstance(project).createTemplate("","",options.getSearchPattern());
+
+ int segmentsCount = template.getSegmentsCount();
+ String text = template.getTemplateText();
+ buf.setLength(0);
+ int prevOffset = 0;
+
+ for(int i=0;i<segmentsCount;++i) {
+ final int offset = template.getSegmentOffset(i);
+ final String name = template.getSegmentName(i);
+
+ final String prefix = prefixProvider.getPrefix(i);
+ if (prefix == null) {
+ throw new MalformedPatternException();
+ }
+
+ buf.append(text.substring(prevOffset,offset));
+ buf.append(prefix);
+ buf.append(name);
+
+ MatchVariableConstraint constraint = options.getVariableConstraint(name);
+ if (constraint==null) {
+ // we do not edited the constraints
+ constraint = new MatchVariableConstraint();
+ constraint.setName( name );
+ options.addVariableConstraint(constraint);
+ }
+
+ SubstitutionHandler handler = result.createSubstitutionHandler(
+ name,
+ prefix + name,
+ constraint.isPartOfSearchResults(),
+ constraint.getMinCount(),
+ constraint.getMaxCount(),
+ constraint.isGreedy()
+ );
+
+ if(constraint.isWithinHierarchy()) {
+ handler.setSubtype(true);
+ }
+
+ if(constraint.isStrictlyWithinHierarchy()) {
+ handler.setStrictSubtype(true);
+ }
+
+ MatchPredicate predicate;
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getRegExp())) {
+ predicate = new RegExpPredicate(
+ constraint.getRegExp(),
+ options.isCaseSensitiveMatch(),
+ name,
+ constraint.isWholeWordsOnly(),
+ constraint.isPartOfSearchResults()
+ );
+ if (constraint.isInvertRegExp()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ if (constraint.isReference()) {
+ predicate = new ReferencePredicate( constraint.getNameOfReferenceVar() );
+
+ if (constraint.isInvertReference()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ Set<MatchPredicate> predicates = new LinkedHashSet<MatchPredicate>();
+ for (MatchPredicateProvider matchPredicateProvider : Extensions.getExtensions(MatchPredicateProvider.EP_NAME)) {
+ matchPredicateProvider.collectPredicates(constraint, name, options, predicates);
+ }
+ for (MatchPredicate matchPredicate : predicates) {
+ addPredicate(handler, matchPredicate);
+ }
+
+ addScriptConstraint(name, constraint, handler);
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getContainsConstraint())) {
+ predicate = new ContainsPredicate(name, constraint.getContainsConstraint());
+ if (constraint.isInvertContainsConstraint()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) {
+ assert false;
+ }
+
+ prevOffset = offset;
+ }
+
+ MatchVariableConstraint constraint = options.getVariableConstraint(Configuration.CONTEXT_VAR_NAME);
+ if (constraint != null) {
+ SubstitutionHandler handler = result.createSubstitutionHandler(
+ Configuration.CONTEXT_VAR_NAME,
+ Configuration.CONTEXT_VAR_NAME,
+ constraint.isPartOfSearchResults(),
+ constraint.getMinCount(),
+ constraint.getMaxCount(),
+ constraint.isGreedy()
+ );
+
+ if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) {
+ MatchPredicate predicate = new WithinPredicate(Configuration.CONTEXT_VAR_NAME, constraint.getWithinConstraint(), project);
+ if (constraint.isInvertWithinConstraint()) {
+ predicate = new NotPredicate(predicate);
+ }
+ addPredicate(handler,predicate);
+ }
+
+ addScriptConstraint(Configuration.CONTEXT_VAR_NAME, constraint, handler);
+ }
+
+ buf.append(text.substring(prevOffset,text.length()));
+
+ PsiElement[] matchStatements;
+
+ try {
+ final String pattern = buf.toString();
+ matchStatements = MatcherImplUtil.createTreeFromText(pattern, PatternTreeContext.Block, options.getFileType(),
+ options.getDialect(), options.getPatternContext(), project, false);
+ if (matchStatements.length==0) throw new MalformedPatternException(pattern);
+ } catch (IncorrectOperationException e) {
+ throw new MalformedPatternException(e.getMessage());
+ }
+
+ NodeFilter filter = LexicalNodesFilter.getInstance();
+
+ GlobalCompilingVisitor compilingVisitor = new GlobalCompilingVisitor();
+ compilingVisitor.compile(matchStatements,context);
+ ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
+
+ for (PsiElement matchStatement : matchStatements) {
+ if (!filter.accepts(matchStatement)) {
+ elements.add(matchStatement);
+ }
+ }
+
+ new DeleteNodesAction(compilingVisitor.getLexicalNodes()).run();
+ return elements;
+ }
+
+ private static void addScriptConstraint(String name, MatchVariableConstraint constraint, SubstitutionHandler handler) {
+ MatchPredicate predicate;
+ if (constraint.getScriptCodeConstraint()!= null && constraint.getScriptCodeConstraint().length() > 2) {
+ final String script = StringUtil.stripQuotesAroundValue(constraint.getScriptCodeConstraint());
+ final String s = ScriptSupport.checkValidScript(script);
+ if (s != null) throw new MalformedPatternException("Script constraint for " + constraint.getName() + " has problem "+s);
+ predicate = new ScriptPredicate(name, script);
+ addPredicate(handler,predicate);
+ }
+ }
+
+ static void addPredicate(SubstitutionHandler handler, MatchPredicate predicate) {
+ if (handler.getPredicate()==null) {
+ handler.setPredicate(predicate);
+ } else {
+ handler.setPredicate(new BinaryPredicate(handler.getPredicate(), predicate, false));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java
new file mode 100644
index 000000000000..0d684503a14d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java
@@ -0,0 +1,436 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.structuralsearch.*;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 17.11.2004
+ * Time: 19:29:05
+ * To change this template use File | Settings | File Templates.
+ */
+class StringToConstraintsTransformer {
+ @NonNls private static final String P_STR = "(\\w+)\\('(\\w+)\\)";
+ private static final Pattern p = Pattern.compile(P_STR);
+ @NonNls private static final String P2_STR = "(\\w+)";
+ private static final Pattern p2 = Pattern.compile(P2_STR);
+ @NonNls private static final String P3_STR = "(\\w+)\\(( ?(?:[\\\"\\*<>!\\.\\?\\:\\$\\\\\\(\\)\\[\\]\\w\\|\\+ =]*|(?:\\\"[^\\\"]*\\\")) ?)\\)";
+ private static final Pattern p3 = Pattern.compile(P3_STR);
+ @NonNls private static final String REF = "ref";
+ @NonNls private static final String READ = "read";
+ @NonNls private static final String WRITE = "write";
+ @NonNls private static final String REGEX = "regex";
+ @NonNls private static final String REGEXW = "regexw";
+ @NonNls private static final String EXPRTYPE = "exprtype";
+ @NonNls private static final String FORMAL = "formal";
+ @NonNls private static final String SCRIPT = "script";
+ @NonNls private static final String CONTAINS = "contains";
+ @NonNls private static final String WITHIN = "within";
+
+ static void transformOldPattern(MatchOptions options) {
+ final String pattern = options.getSearchPattern();
+
+ final StringBuilder buf = new StringBuilder();
+
+ StringBuilder miscBuffer = null;
+ int anonymousTypedVarsCount = 0;
+
+ for(int index=0;index < pattern.length();++index) {
+ char ch = pattern.charAt(index);
+
+ if (ch=='\'') {
+ // doubling '
+ final int length = pattern.length();
+ if (index + 1 < length &&
+ pattern.charAt(index + 1)=='\''
+ ) {
+ // ignore next '
+ index++;
+ } else if (index + 2 < length &&
+ pattern.charAt(index + 2)=='\''
+ ) {
+ // eat simple character
+ buf.append(ch);
+ buf.append(pattern.charAt(++index));
+ ch = pattern.charAt(++index);
+ } else if (index + 3 < length &&
+ pattern.charAt(index + 1)=='\\' &&
+ pattern.charAt(index + 3)=='\''
+ ) {
+ // eat simple escape character
+ buf.append(ch);
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ ch = pattern.charAt(++index);
+ } else if (index + 7 < length &&
+ pattern.charAt(index + 1)=='\\' &&
+ pattern.charAt(index + 2)=='u' &&
+ pattern.charAt(index + 7)=='\'') {
+ // eat simple escape character
+ buf.append(ch);
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ buf.append(pattern.charAt(++index));
+ ch = pattern.charAt(++index);
+ } else {
+ // typed variable
+
+ buf.append("$");
+ if (miscBuffer == null) miscBuffer = new StringBuilder();
+ else miscBuffer.setLength(0);
+
+ // eat the name of typed var
+ for(++index; index< length && Character.isJavaIdentifierPart(ch = pattern.charAt(index)); ++index) {
+ miscBuffer.append(ch);
+ buf.append(ch);
+ }
+
+ boolean anonymous = false;
+
+ if (miscBuffer.length() == 0) throw new MalformedPatternException(SSRBundle.message("error.expected.character"));
+ if (miscBuffer.charAt(0)=='_') {
+ anonymous = true;
+
+ if(miscBuffer.length() == 1) {
+ // anonymous var, make it unique for the case of constraints
+ anonymousTypedVarsCount++;
+ miscBuffer.append(anonymousTypedVarsCount);
+ buf.append(anonymousTypedVarsCount);
+ } else {
+ buf.deleteCharAt(buf.length()-miscBuffer.length());
+ miscBuffer.deleteCharAt(0);
+ }
+ }
+
+
+ buf.append("$");
+ String typedVar = miscBuffer.toString();
+ int minOccurs = 1;
+ int maxOccurs = 1;
+ boolean greedy = true;
+ MatchVariableConstraint constraint = options.getVariableConstraint(typedVar);
+ boolean constraintCreated = false;
+
+ if (constraint==null) {
+ constraint = new MatchVariableConstraint();
+ constraint.setName( typedVar );
+ constraintCreated = true;
+ }
+
+ // Check the number of occurrences for typed variable
+ if (index < length) {
+ char possibleQuantifier = pattern.charAt(index);
+
+ if (possibleQuantifier=='+') {
+ maxOccurs = Integer.MAX_VALUE;
+ ++index;
+ } else if (possibleQuantifier=='?') {
+ minOccurs = 0;
+ ++index;
+ } else if (possibleQuantifier=='*') {
+ minOccurs = 0;
+ maxOccurs = Integer.MAX_VALUE;
+ ++index;
+ } else if (possibleQuantifier=='{') {
+ ++index;
+ minOccurs = 0;
+ while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') {
+ minOccurs *= 10;
+ minOccurs += (ch - '0');
+ if (minOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow"));
+ ++index;
+ }
+
+ if (ch==',') {
+ ++index;
+ maxOccurs = 0;
+
+ while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') {
+ maxOccurs *= 10;
+ maxOccurs += (ch - '0');
+ if (maxOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow"));
+ ++index;
+ }
+ } else {
+ maxOccurs = Integer.MAX_VALUE;
+ }
+
+ if (ch != '}') {
+ if (maxOccurs == Integer.MAX_VALUE) throw new MalformedPatternException(SSRBundle.message("error.expected.brace1"));
+ else throw new MalformedPatternException(SSRBundle.message("error.expected.brace2"));
+ }
+ ++index;
+ }
+
+ if (index < length) {
+ ch = pattern.charAt(index);
+ if (ch=='?') {
+ greedy = false;
+ ++index;
+ }
+ }
+ }
+
+ if (constraintCreated) {
+ constraint.setMinCount(minOccurs);
+ constraint.setMaxCount(maxOccurs);
+ constraint.setGreedy(greedy);
+ constraint.setPartOfSearchResults(!anonymous);
+ }
+
+ if (index < length && pattern.charAt(index) == ':') {
+ ++index;
+ if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ':'));
+ ch = pattern.charAt(index);
+ if (ch == ':') {
+ // double colon instead of condition
+ buf.append(ch);
+ }
+ else {
+ index = eatTypedVarCondition(index, pattern, miscBuffer, constraint);
+ }
+ }
+
+ if (constraintCreated) {
+ if (constraint.getWithinConstraint().length() > 0) {
+ constraint.setName(Configuration.CONTEXT_VAR_NAME);
+ }
+ options.addVariableConstraint(constraint);
+ }
+
+ if (index == length) break;
+ // fall through to append white space
+ ch = pattern.charAt(index);
+ }
+ }
+
+ buf.append(ch);
+ }
+
+ options.setSearchPattern( buf.toString() );
+ }
+
+ private static int eatTypedVarCondition(int index,
+ String pattern,
+ StringBuilder miscBuffer,
+ MatchVariableConstraint constraint) {
+ final int length = pattern.length();
+
+ char ch = pattern.charAt(index);
+ if (ch == '+' || ch == '*') {
+ // this is type axis navigation relation
+ switch(ch) {
+ case '+':
+ constraint.setStrictlyWithinHierarchy(true);
+ break;
+ case '*':
+ constraint.setWithinHierarchy(true);
+ break;
+ }
+
+ ++index;
+ if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ch));
+ ch = pattern.charAt(index);
+ }
+
+ if (ch == '[') {
+ // eat complete condition
+
+ miscBuffer.setLength(0);
+ for(++index; index < length && ((ch = pattern.charAt(index))!=']' || pattern.charAt(index-1)=='\\'); ++index) {
+ miscBuffer.append(ch);
+ }
+ if (ch != ']') throw new MalformedPatternException(SSRBundle.message("error.expected.condition.or.bracket"));
+ ++index;
+ parseCondition(constraint, miscBuffer.toString());
+ } else {
+ // eat reg exp constraint
+ miscBuffer.setLength(0);
+ index = handleRegExp(index, pattern, miscBuffer, constraint);
+ }
+ return index;
+ }
+
+ private static int handleRegExp(int index,
+ String pattern,
+ StringBuilder miscBuffer,
+ MatchVariableConstraint constraint) {
+ char c = pattern.charAt(index - 1);
+ final int length = pattern.length();
+ for(char ch; index < length && !Character.isWhitespace(ch = pattern.charAt(index)); ++index) {
+ miscBuffer.append(ch);
+ }
+
+ if (miscBuffer.length() == 0)
+ if (c == ':') throw new MalformedPatternException(SSRBundle.message("error.expected.condition", c));
+ else return index;
+ String regexp = miscBuffer.toString();
+
+ if (constraint.getRegExp()!=null &&
+ constraint.getRegExp().length() > 0 &&
+ !constraint.getRegExp().equals(regexp)) {
+ throw new MalformedPatternException(SSRBundle.message("error.two.different.type.constraints"));
+ } else {
+ try {
+ Pattern.compile(regexp);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setRegExp(regexp);
+ }
+
+ return index;
+ }
+
+ private static void parseCondition(MatchVariableConstraint constraint, String condition) {
+ if (condition.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "["));
+ final List<String> tokens = StringUtil.split(condition, "&&", true, false);
+
+ for (String token : tokens) {
+ token = token.trim();
+ if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "&&"));
+ boolean hasNot = false;
+ boolean consumed = false;
+
+ if (StringUtil.startsWithChar(token, '!')) {
+ token = token.substring(1);
+ if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "!"));
+ hasNot = true;
+ }
+
+ Matcher m = p.matcher(token);
+
+ if (m.matches()) {
+ String option = m.group(1);
+
+ if (option.equalsIgnoreCase(REF)) {
+ String name = m.group(2);
+
+ constraint.setReference(true);
+ constraint.setInvertReference(hasNot);
+ constraint.setNameOfReferenceVar(name);
+ consumed = true;
+ }
+ } else {
+ m = p2.matcher(token);
+
+ if (m.matches()) {
+ String option = m.group(1);
+
+ if (option.equalsIgnoreCase(READ)) {
+ constraint.setReadAccess(true);
+ constraint.setInvertReadAccess(hasNot);
+ consumed = true;
+ } else if (option.equalsIgnoreCase(WRITE)) {
+ constraint.setWriteAccess(true);
+ constraint.setInvertWriteAccess(hasNot);
+ consumed = true;
+ }
+ } else {
+ m = p3.matcher(token);
+
+ if (m.matches()) {
+ String option = m.group(1);
+
+ if (option.equalsIgnoreCase(REGEX) || option.equalsIgnoreCase(REGEXW)) {
+ String typePattern = getSingleParameter(m, SSRBundle.message("reg.exp.should.be.delimited.with.spaces.error.message"));
+ if (typePattern.isEmpty()) throw new MalformedPatternException(SSRBundle.message("no.reg.exp.specified.error.message"));
+
+ if (StringUtil.startsWithChar(typePattern, '*')) {
+ typePattern = typePattern.substring(1);
+ constraint.setWithinHierarchy(true);
+ }
+ try {
+ Pattern.compile(typePattern);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setRegExp( typePattern );
+ constraint.setInvertRegExp( hasNot );
+ consumed = true;
+ if (option.equalsIgnoreCase(REGEXW)) {
+ constraint.setWholeWordsOnly(true);
+ }
+ } else if (option.equalsIgnoreCase(EXPRTYPE)) {
+ String exprTypePattern = getSingleParameter(m, SSRBundle.message(
+ "reg.exp.in.expr.type.should.be.delimited.with.spaces.error.message"));
+
+ if (StringUtil.startsWithChar(exprTypePattern, '*')) {
+ exprTypePattern = exprTypePattern.substring(1);
+ constraint.setExprTypeWithinHierarchy(true);
+ }
+ try {
+ Pattern.compile(exprTypePattern);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setNameOfExprType( exprTypePattern );
+ constraint.setInvertExprType( hasNot );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(FORMAL)) {
+ String exprTypePattern = getSingleParameter(m, SSRBundle.message(
+ "reg.exp.in.formal.arg.type.should.be.delimited.with.spaces.error.message"));
+
+ if (StringUtil.startsWithChar(exprTypePattern, '*')) {
+ exprTypePattern = exprTypePattern.substring(1);
+ constraint.setFormalArgTypeWithinHierarchy(true);
+ }
+ try {
+ Pattern.compile(exprTypePattern);
+ } catch (PatternSyntaxException e) {
+ throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
+ }
+ constraint.setNameOfFormalArgType( exprTypePattern );
+ constraint.setInvertFormalType( hasNot );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(SCRIPT)) {
+ String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
+
+ constraint.setScriptCodeConstraint( script );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(CONTAINS)) {
+ if (hasNot) constraint.setInvertContainsConstraint(true);
+ String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
+
+ constraint.setContainsConstraint(script );
+ consumed = true;
+ } else if (option.equalsIgnoreCase(WITHIN)) {
+ if (hasNot) constraint.setInvertWithinConstraint(true);
+ String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
+
+ constraint.setWithinConstraint(script);
+ consumed = true;
+ }
+ }
+ }
+ }
+
+ if (!consumed) {
+ throw new UnsupportedPatternException(
+ SSRBundle.message("option.is.not.recognized.error.message", token)
+ );
+ }
+ }
+ }
+
+ private static String getSingleParameter(Matcher m, String errorMessage) {
+ final String value = m.group(2);
+ if (value.isEmpty()) return value;
+
+ if (value.charAt(0)!=' ' || value.charAt(value.length()-1)!=' ') {
+ throw new MalformedPatternException(errorMessage);
+ }
+ return value.trim();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java
new file mode 100644
index 000000000000..e341b57da8ce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java
@@ -0,0 +1,70 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiFile;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class TestModeOptimizingSearchHelper extends OptimizingSearchHelperBase {
+ private static String lastString;
+ private final StringBuilder builder = new StringBuilder();
+ private int lastLength;
+
+ TestModeOptimizingSearchHelper(CompileContext _context) {
+ super(_context);
+ }
+
+ public boolean doOptimizing() {
+ return true;
+ }
+
+ public void clear() {
+ lastString = builder.toString();
+ builder.setLength(0);
+ lastLength = 0;
+ }
+
+ protected void doAddSearchWordInCode(final String refname) {
+ append(refname, "reserved in code:");
+ }
+
+ @Override
+ protected void doAddSearchWordInText(String refname) {
+ append(refname, "in text:");
+ }
+
+ private void append(final String refname, final String str) {
+ if (builder.length() == lastLength) builder.append("[");
+ else builder.append("|");
+ builder.append(str).append(refname);
+ }
+
+ protected void doAddSearchWordInComments(final String refname) {
+ append(refname, "in comments:");
+ }
+
+ protected void doAddSearchWordInLiterals(final String refname) {
+ append(refname, "in literals:");
+ }
+
+ public void endTransaction() {
+ super.endTransaction();
+ builder.append("]");
+ lastLength = builder.length();
+ }
+
+ public boolean isScannedSomething() {
+ return false;
+ }
+
+ public Set<PsiFile> getFilesSetToScan() {
+ return Collections.emptySet();
+ }
+
+ public String getSearchPlan() {
+ return lastString;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java
new file mode 100644
index 000000000000..99917ad23240
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java
@@ -0,0 +1,53 @@
+package com.intellij.structuralsearch.impl.matcher.compiler;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.XmlRecursiveElementVisitor;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.psi.xml.XmlText;
+import com.intellij.psi.xml.XmlToken;
+import com.intellij.structuralsearch.impl.matcher.filters.TagValueFilter;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler;
+import com.intellij.structuralsearch.impl.matcher.handlers.XmlTextHandler;
+import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlCompilingVisitor extends XmlRecursiveElementVisitor {
+ private final GlobalCompilingVisitor myCompilingVisitor;
+
+ public XmlCompilingVisitor(GlobalCompilingVisitor compilingVisitor) {
+ this.myCompilingVisitor = compilingVisitor;
+ }
+
+ @Override public void visitElement(PsiElement element) {
+ myCompilingVisitor.handle(element);
+ super.visitElement(element);
+ }
+
+ @Override public void visitXmlToken(XmlToken token) {
+ super.visitXmlToken(token);
+
+ if (token.getParent() instanceof XmlText && myCompilingVisitor.getContext().getPattern().isRealTypedVar(token)) {
+ final MatchingHandler handler = myCompilingVisitor.getContext().getPattern().getHandler(token);
+ handler.setFilter(TagValueFilter.getInstance());
+
+ final XmlTextHandler parentHandler = new XmlTextHandler();
+ myCompilingVisitor.getContext().getPattern().setHandler(token.getParent(), parentHandler);
+ parentHandler.setFilter(TagValueFilter.getInstance());
+ }
+ }
+
+ @Override public void visitXmlTag(XmlTag xmlTag) {
+ myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() + 1);
+ super.visitXmlTag(xmlTag);
+ myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() - 1);
+
+ if (myCompilingVisitor.getCodeBlockLevel() == 1) {
+ myCompilingVisitor.getContext().getPattern().setStrategy(XmlMatchingStrategy.getInstance());
+ myCompilingVisitor.getContext().getPattern()
+ .setHandler(xmlTag, new TopLevelMatchingHandler(myCompilingVisitor.getContext().getPattern().getHandler(xmlTag)));
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java
new file mode 100644
index 000000000000..db962ed8edf1
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java
@@ -0,0 +1,27 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim
+ * Date: 28.12.2003
+ * Time: 0:13:19
+ * To change this template use Options | File Templates.
+ */
+public class CompositeFilter implements NodeFilter {
+ private final NodeFilter first;
+ private final NodeFilter second;
+ protected boolean result;
+
+ public boolean accepts(PsiElement element) {
+ return first.accepts(element) ||
+ second.accepts(element);
+ }
+
+ public CompositeFilter(NodeFilter _first, NodeFilter _second) {
+ first = _first;
+ second = _second;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java
new file mode 100644
index 000000000000..267f37699945
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java
@@ -0,0 +1,16 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.impl.source.tree.LeafElement;
+
+/**
+ * Default searching filter
+ */
+public class DefaultFilter {
+ public static boolean accepts(PsiElement element, PsiElement element2) {
+ if (element instanceof LeafElement && element2 instanceof LeafElement) {
+ return ((LeafElement)element).getElementType() == ((LeafElement)element2).getElementType();
+ }
+ return element.getClass()==element2.getClass();
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java
new file mode 100644
index 000000000000..3cef402b09d3
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java
@@ -0,0 +1,51 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+
+/**
+ * Filter for lexical nodes
+ */
+public final class LexicalNodesFilter implements NodeFilter {
+ private boolean careKeyWords;
+ private boolean result;
+
+ private LexicalNodesFilter() {}
+
+ public static NodeFilter getInstance() {
+ return NodeFilterHolder.instance;
+ }
+
+ public boolean getResult() {
+ return result;
+ }
+
+ public void setResult(boolean result) {
+ this.result = result;
+ }
+
+ private static class NodeFilterHolder {
+ private static final NodeFilter instance = new LexicalNodesFilter();
+ }
+
+ public boolean isCareKeyWords() {
+ return careKeyWords;
+ }
+
+ public void setCareKeyWords(boolean careKeyWords) {
+ this.careKeyWords = careKeyWords;
+ }
+
+ public boolean accepts(PsiElement element) {
+ result = false;
+ if (element!=null) {
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile != null) {
+ element.accept(profile.getLexicalNodesFilter(this));
+ }
+ }
+ return result;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java
new file mode 100644
index 000000000000..97ed9627e3fc
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java
@@ -0,0 +1,43 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.xml.XmlTag;
+import com.intellij.psi.xml.XmlText;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: maxim.mossienko
+ * Date: Oct 12, 2005
+ * Time: 4:44:19 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class TagValueFilter extends XmlElementVisitor implements NodeFilter {
+ private boolean result;
+
+ @Override public void visitXmlText(XmlText text) {
+ result = true;
+ }
+
+ @Override public void visitXmlTag(XmlTag tag) {
+ result = true;
+ }
+
+ private static class NodeFilterHolder {
+ private static final NodeFilter instance = new TagValueFilter();
+ }
+
+ public static NodeFilter getInstance() {
+ return NodeFilterHolder.instance;
+ }
+
+ private TagValueFilter() {
+ }
+
+ public boolean accepts(PsiElement element) {
+ result = false;
+ if (element!=null) element.accept(this);
+ return result;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java
new file mode 100644
index 000000000000..89c295ce1d80
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java
@@ -0,0 +1,19 @@
+package com.intellij.structuralsearch.impl.matcher.filters;
+
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.xml.XmlText;
+
+/**
+* @author Eugene.Kudelevsky
+*/
+public class XmlLexicalNodesFilter extends XmlElementVisitor {
+ private final LexicalNodesFilter myLexicalNodesFilter;
+
+ public XmlLexicalNodesFilter(LexicalNodesFilter lexicalNodesFilter) {
+ this.myLexicalNodesFilter = lexicalNodesFilter;
+ }
+
+ @Override public void visitXmlText(XmlText text) {
+ myLexicalNodesFilter.setResult(true);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java
new file mode 100644
index 000000000000..c95a85623753
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java
@@ -0,0 +1,8 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public interface DelegatingHandler {
+ MatchingHandler getDelegate();
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java
new file mode 100644
index 000000000000..34941cfa0257
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java
@@ -0,0 +1,46 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import org.jetbrains.annotations.NotNull;
+
+public final class LightTopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler {
+ private final MatchingHandler myDelegate;
+
+ public LightTopLevelMatchingHandler(@NotNull MatchingHandler delegate) {
+ myDelegate = delegate;
+ }
+
+ public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) {
+ return myDelegate.match(patternNode, matchedNode, matchContext);
+ }
+
+ @Override
+ public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) {
+ return myDelegate.canMatch(patternNode, matchedNode);
+ }
+
+ @Override
+ public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) {
+ return myDelegate.matchSequentially(nodes, nodes2, context);
+ }
+
+ public boolean match(final PsiElement patternNode,
+ final PsiElement matchedNode, final int start, final int end, final MatchContext context) {
+ return myDelegate.match(patternNode, matchedNode, start, end, context);
+ }
+
+ public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) {
+ return myDelegate.shouldAdvanceTheMatchFor(patternElement, matchedElement);
+ }
+
+ public MatchingHandler getDelegate() {
+ return myDelegate;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java
new file mode 100644
index 000000000000..78967106755f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java
@@ -0,0 +1,49 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.List;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Maxim.Mossienko
+ * Date: Jun 30, 2004
+ * Time: 5:07:33 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class LiteralWithSubstitutionHandler extends MatchingHandler {
+ private final String matchExpression;
+ private Matcher matcher;
+ private final List<SubstitutionHandler> handlers;
+
+ public LiteralWithSubstitutionHandler(String _matchedExpression,List<SubstitutionHandler> _handlers) {
+ matchExpression = _matchedExpression;
+ handlers = _handlers;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ final String text = RegExpPredicate.getMeaningfulText(matchedNode);
+ int offset = matchedNode.getText().indexOf(text);
+ if (matcher==null) {
+ matcher = Pattern.compile(matchExpression).matcher(text);
+ } else {
+ matcher.reset(text);
+ }
+
+ while (matcher.find()) {
+ for (int i = 0; i < handlers.size(); ++i) {
+ SubstitutionHandler handler = handlers.get(i);
+
+ if (!handler.handle(matchedNode,offset + matcher.start(i+1), offset + matcher.end(i+1),context)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java
new file mode 100644
index 000000000000..cba96681ab71
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java
@@ -0,0 +1,27 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public abstract class MatchPredicate {
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) {
+ return match(patternNode,matchedNode,context);
+ }
+
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public abstract boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context);
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java
new file mode 100644
index 000000000000..c53b08412b6b
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java
@@ -0,0 +1,271 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.impl.matcher.filters.DefaultFilter;
+import com.intellij.structuralsearch.impl.matcher.predicates.BinaryPredicate;
+import com.intellij.structuralsearch.impl.matcher.predicates.NotPredicate;
+import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
+import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Root of handlers for pattern node matching. Handles simplest type of the match.
+ */
+public abstract class MatchingHandler extends MatchPredicate {
+ protected NodeFilter filter;
+ private PsiElement pinnedElement;
+
+ public void setFilter(NodeFilter filter) {
+ this.filter = filter;
+ }
+
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successful and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) {
+ return match(patternNode,matchedNode,context);
+ }
+
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successful and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) {
+ if (patternNode == null) {
+ return matchedNode == null;
+ }
+
+ return canMatch(patternNode, matchedNode);
+ }
+
+ public boolean canMatch(final PsiElement patternNode, final PsiElement matchedNode) {
+ if (filter!=null) {
+ return filter.accepts(matchedNode);
+ } else {
+ return DefaultFilter.accepts(patternNode, matchedNode);
+ }
+ }
+
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ PsiElement patternElement;
+ MatchingHandler handler;
+ MatchingStrategy strategy = context.getPattern().getStrategy();
+
+ skipIfNecessary(nodes, nodes2, strategy);
+ skipIfNecessary(nodes2, nodes, strategy);
+
+ if (nodes2.hasNext() &&
+ (handler = context.getPattern().getHandler(nodes.current())).match(patternElement = nodes.current(),nodes2.current(),context)) {
+
+ nodes.advance();
+
+ if (shouldAdvanceTheMatchFor(patternElement, nodes2.current())) {
+ nodes2.advance();
+ skipIfNecessary(nodes, nodes2, strategy);
+ }
+ skipIfNecessary(nodes2, nodes, strategy);
+
+ if (nodes.hasNext()) {
+ final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
+
+ if (nextHandler.matchSequentially(nodes,nodes2,context)) {
+ // match was found!
+ return true;
+ } else {
+ // rewind, we was not able to match descendants
+ nodes.rewind();
+ nodes2.rewind();
+ }
+ } else {
+ // match was found
+ return handler.isMatchSequentiallySucceeded(nodes2);
+ }
+ }
+ return false;
+ }
+
+ private static void skipIfNecessary(NodeIterator nodes, NodeIterator nodes2, MatchingStrategy strategy) {
+ while (strategy.shouldSkip(nodes2.current(), nodes.current())) {
+ nodes2.advance();
+ }
+ }
+
+ protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return !nodes2.hasNext();
+ }
+
+ private static MatchPredicate findRegExpPredicate(MatchPredicate start) {
+ if (start==null) return null;
+ if (start instanceof RegExpPredicate) return start;
+
+ if(start instanceof BinaryPredicate) {
+ BinaryPredicate binary = (BinaryPredicate)start;
+ final MatchPredicate result = findRegExpPredicate(binary.getFirst());
+ if (result!=null) return result;
+
+ return findRegExpPredicate(binary.getSecond());
+ } else if (start instanceof NotPredicate) {
+ return null;
+ }
+ return null;
+ }
+
+ public static RegExpPredicate getSimpleRegExpPredicate(SubstitutionHandler handler) {
+ if (handler == null) return null;
+ return (RegExpPredicate)findRegExpPredicate(handler.getPredicate());
+ }
+
+ static class ClearStateVisitor extends PsiRecursiveElementWalkingVisitor {
+ private CompiledPattern pattern;
+
+ ClearStateVisitor() {
+ super(true);
+ }
+
+ @Override public void visitElement(PsiElement element) {
+ // We do not reset certain handlers because they are also bound to higher level nodes
+ // e.g. Identifier handler in name is also bound to PsiMethod
+ if (pattern.isToResetHandler(element)) {
+ MatchingHandler handler = pattern.getHandlerSimple(element);
+ if (handler instanceof SubstitutionHandler) {
+ handler.reset();
+ }
+ }
+ super.visitElement(element);
+ }
+
+ synchronized void clearState(CompiledPattern _pattern, PsiElement el) {
+ pattern = _pattern;
+ el.acceptChildren(this);
+ pattern = null;
+ }
+ }
+
+ protected static ClearStateVisitor clearingVisitor = new ClearStateVisitor();
+
+ public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) {
+ final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null;
+ context.setResult(null);
+
+ try {
+
+ if (patternNodes.hasNext() && !matchedNodes.hasNext()) {
+ return validateSatisfactionOfHandlers(patternNodes, context);
+ }
+
+ Set<PsiElement> matchedElements = null;
+
+ for(; patternNodes.hasNext(); patternNodes.advance()) {
+ final PsiElement patternNode = patternNodes.current();
+ final CompiledPattern pattern = context.getPattern();
+ final MatchingHandler handler = pattern.getHandler(patternNode);
+
+ final PsiElement startMatching = matchedNodes.current();
+ do {
+ final PsiElement element = handler.getPinnedNode(null);
+ final PsiElement matchedNode = element != null ? element : matchedNodes.current();
+
+ if (element == null) matchedNodes.advance();
+ if (!matchedNodes.hasNext()) matchedNodes.reset();
+
+ if (matchedElements == null || !matchedElements.contains(matchedNode)) {
+
+ if (handler.match(patternNode, matchedNode, context)) {
+ if (matchedElements == null) matchedElements = new HashSet<PsiElement>();
+ matchedElements.add(matchedNode);
+ if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) {
+ break;
+ }
+ } else if (element != null) {
+ return false;
+ }
+
+ // clear state of dependent objects
+ clearingVisitor.clearState(pattern, patternNode);
+ }
+
+ // passed of elements and does not found the match
+ if (startMatching == matchedNodes.current()) {
+ final boolean result = validateSatisfactionOfHandlers(patternNodes,context);
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ }
+ } while(true);
+
+ if (!handler.shouldAdvanceThePatternFor(patternNode, null)) {
+ patternNodes.rewind();
+ }
+ }
+
+ final boolean result = validateSatisfactionOfHandlers(patternNodes, context);
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ } finally {
+ if (saveResult!=null) {
+ if (context.hasResult()) {
+ saveResult.getMatches().addAll(context.getResult().getMatches());
+ }
+ context.setResult(saveResult);
+ }
+ }
+ }
+
+ protected static boolean validateSatisfactionOfHandlers(NodeIterator nodes, MatchContext context) {
+ while(nodes.hasNext()) {
+ final PsiElement element = nodes.current();
+ final MatchingHandler handler = context.getPattern().getHandler( element );
+
+ if (handler instanceof SubstitutionHandler) {
+ if (!((SubstitutionHandler)handler).validate(
+ context, StructuralSearchUtil.getProfileByPsiElement(element).getElementContextByPsi(element))) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ nodes.advance();
+ }
+ return true;
+ }
+
+ public NodeFilter getFilter() {
+ return filter;
+ }
+
+ public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) {
+ return true;
+ }
+
+ public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) {
+ return true;
+ }
+
+ public void reset() {
+ //pinnedElement = null;
+ }
+
+ public PsiElement getPinnedNode(PsiElement context) {
+ return pinnedElement;
+ }
+
+ public void setPinnedElement(final PsiElement pinnedElement) {
+ this.pinnedElement = pinnedElement;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java
new file mode 100644
index 000000000000..3dffe792e308
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java
@@ -0,0 +1,20 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public final class SimpleHandler extends MatchingHandler {
+ /**
+ * Matches given handler node against given value.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) {
+ if (!super.match(patternNode,matchedNode,context)) return false;
+ return context.getMatcher().match(patternNode,matchedNode);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java
new file mode 100644
index 000000000000..389c4a7916c2
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java
@@ -0,0 +1,108 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.equivalence.EquivalenceDescriptor;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.DuplocatorUtil;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class SkippingHandler extends MatchingHandler implements DelegatingHandler {
+
+ private final MatchingHandler myDelegate;
+
+ public SkippingHandler(@NotNull MatchingHandler delegate) {
+ myDelegate = delegate;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, final MatchContext matchContext) {
+ if (patternNode == null || matchedNode == null || matchedNode.getClass() == patternNode.getClass()) {
+ return myDelegate.match(patternNode, matchedNode, matchContext);
+ }
+
+ /*if (patternNode != null && matchedNode != null && patternNode.getClass() == matchedNode.getClass()) {
+ //return myDelegate.match(patternNode, matchedNode, matchContext);
+ }*/
+ PsiElement newPatternNode = skipNodeIfNeccessary(patternNode);
+ matchedNode = skipNodeIfNeccessary(matchedNode);
+
+ if (newPatternNode != patternNode) {
+ return matchContext.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, matchContext);
+ }
+
+ return myDelegate.match(patternNode, matchedNode, matchContext);
+ }
+
+ @Override
+ public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) {
+ return myDelegate.canMatch(patternNode, matchedNode);
+ }
+
+ @Override
+ public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) {
+ return myDelegate.matchSequentially(nodes, nodes2, context);
+ }
+
+ public boolean match(PsiElement patternNode,
+ PsiElement matchedNode,
+ final int start,
+ final int end,
+ final MatchContext context) {
+ if (patternNode == null || matchedNode == null || patternNode.getClass() == matchedNode.getClass()) {
+ return myDelegate.match(patternNode, matchedNode, start, end, context);
+ }
+
+ PsiElement newPatternNode = skipNodeIfNeccessary(patternNode);
+ matchedNode = skipNodeIfNeccessary(matchedNode);
+
+ if (newPatternNode != patternNode) {
+ return context.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, start, end, context);
+ }
+
+ return myDelegate.match(patternNode, matchedNode, start, end, context);
+ }
+
+ protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return myDelegate.isMatchSequentiallySucceeded(nodes2);
+ }
+
+ @Override
+ public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) {
+ return true;
+ }
+
+ public MatchingHandler getDelegate() {
+ return myDelegate;
+ }
+
+ @Nullable
+ public static PsiElement getOnlyNonWhitespaceChild(PsiElement element) {
+ PsiElement onlyChild = null;
+ for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
+ if (DuplocatorUtil.isIgnoredNode(element) || child.getTextLength() == 0) {
+ continue;
+ }
+ if (onlyChild != null) {
+ return null;
+ }
+ onlyChild = child;
+ }
+ return onlyChild;
+ }
+
+ @Nullable
+ public static PsiElement skipNodeIfNeccessary(PsiElement element) {
+ return skipNodeIfNeccessary(element, null, null);
+ }
+
+ @Nullable
+ public static PsiElement skipNodeIfNeccessary(PsiElement element, EquivalenceDescriptor descriptor, NodeFilter filter) {
+ return DuplocatorUtil.skipNodeIfNeccessary(element, descriptor, filter != null ? filter : LexicalNodesFilter.getInstance());
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java
new file mode 100644
index 000000000000..0c84e3bb2436
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java
@@ -0,0 +1,557 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.FilteringNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.plugin.ui.Configuration;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Matching handler that manages substitutions matching
+ */
+public class SubstitutionHandler extends MatchingHandler {
+ private final String name;
+ private final int maxOccurs;
+ private final int minOccurs;
+ private final boolean greedy;
+ private boolean target;
+ private MatchPredicate predicate;
+ private MatchingHandler matchHandler;
+ private boolean subtype;
+ private boolean strictSubtype;
+ // matchedOccurs + 1 = number of item being matched
+ private int matchedOccurs;
+ private int totalMatchedOccurs = -1;
+ private MatchResultImpl myNestedResult;
+
+ private static final NodeFilter VARS_DELIM_FILTER = new NodeFilter() {
+ @Override
+ public boolean accepts(PsiElement element) {
+ if (element == null) {
+ return false;
+ }
+
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
+ if (profile == null) {
+ return false;
+ }
+
+ return profile.canBeVarDelimeter(element);
+ }
+ };
+
+ public SubstitutionHandler(final String name, final boolean target, int minOccurs,
+ int maxOccurs, boolean greedy) {
+ this.name = name;
+ this.maxOccurs = maxOccurs;
+ this.minOccurs = minOccurs;
+ this.target = target;
+ this.greedy = greedy;
+ }
+
+ public SubstitutionHandler(final SubstitutionHandler substitutionHandler) {
+ this(substitutionHandler.getName(),substitutionHandler.isTarget(), substitutionHandler.getMinOccurs(),
+ substitutionHandler.getMaxOccurs(), substitutionHandler.greedy);
+ }
+
+ public boolean isSubtype() {
+ return subtype;
+ }
+
+ public boolean isStrictSubtype() {
+ return strictSubtype;
+ }
+
+ public void setStrictSubtype(boolean strictSubtype) {
+ this.strictSubtype = strictSubtype;
+ }
+
+ public void setSubtype(boolean subtype) {
+ this.subtype = subtype;
+ }
+
+ public void setPredicate(MatchPredicate handler) {
+ predicate = handler;
+ }
+
+ // Matcher
+
+ public MatchPredicate getPredicate() {
+ return predicate;
+ }
+
+ private static boolean validateOneMatch(final PsiElement match, int start, int end, final MatchResultImpl result, final MatchContext matchContext) {
+ final boolean matchresult;
+
+ if (match!=null) {
+ if (start==0 && end==-1 && result.getStart()==0 && result.getEnd()==-1) {
+ matchresult = matchContext.getMatcher().match(match,result.getMatchRef().getElement());
+ } else {
+ matchresult = StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end).equals(
+ result.getMatchImage()
+ );
+ }
+ } else {
+ matchresult = result.isMatchImageNull();
+ }
+
+ return matchresult;
+ }
+
+ public boolean validate(final PsiElement match, int start, int end, MatchContext context) {
+ if (predicate!=null) {
+ if(!predicate.match(null,match,start,end,context)) return false;
+ }
+
+ if (maxOccurs==0) {
+ totalMatchedOccurs++;
+ return false;
+ }
+
+ MatchResultImpl result = context.getResult().findSon(name);
+
+ if (result == null && context.getPreviousResult() != null) {
+ result = context.getPreviousResult().findSon(name);
+ }
+
+ if (result!=null) {
+ if (minOccurs == 1 && maxOccurs == 1) {
+ // check if they are the same
+ return validateOneMatch(match, start, end, result,context);
+ } else if (maxOccurs > 1 && totalMatchedOccurs!=-1) {
+ final int size = result.getAllSons().size();
+ if (matchedOccurs >= size) {
+ return false;
+ }
+ result = size == 0 ?result:(MatchResultImpl)result.getAllSons().get(matchedOccurs);
+ // check if they are the same
+ return validateOneMatch(match, start, end, result, context);
+ }
+ }
+
+ return true;
+ }
+
+ public boolean match(final PsiElement node, final PsiElement match, MatchContext context) {
+ if (!super.match(node,match,context)) return false;
+
+ return matchHandler == null ?
+ context.getMatcher().match(node, match):
+ matchHandler.match(node,match,context);
+ }
+
+ public boolean handle(final PsiElement match, MatchContext context) {
+ return handle(match,0,-1,context);
+ }
+
+ public void addResult(PsiElement match,int start, int end,MatchContext context) {
+ if (totalMatchedOccurs == -1) {
+ final MatchResultImpl matchResult = context.getResult();
+ final MatchResultImpl substitution = matchResult.findSon(name);
+
+ if (substitution == null) {
+ matchResult.addSon( createMatch(match,start,end) );
+ } else if (maxOccurs > 1) {
+ final MatchResultImpl result = createMatch(match,start,end);
+
+ if (!substitution.isMultipleMatch()) {
+ // adding intermediate node to contain all multiple matches
+ MatchResultImpl sonresult = new MatchResultImpl(
+ substitution.getName(),
+ substitution.getMatchImage(),
+ substitution.getMatchRef(),
+ substitution.getStart(),
+ substitution.getEnd(),
+ target
+ );
+
+ sonresult.setParent(substitution);
+ substitution.setMatchRef(
+ new SmartPsiPointer(match == null ? null : match)
+ );
+
+ substitution.setMultipleMatch(true);
+
+ if (substitution.isScopeMatch()) {
+ substitution.setScopeMatch(false);
+ sonresult.setScopeMatch(true);
+ for(MatchResult r:substitution.getAllSons()) sonresult.addSon((MatchResultImpl)r);
+ substitution.clearMatches();
+ }
+
+ substitution.addSon( sonresult);
+ }
+
+ result.setParent(substitution);
+ substitution.addSon( result );
+ }
+ }
+ }
+
+ public boolean handle(final PsiElement match, int start, int end, MatchContext context) {
+ if (!validate(match,start,end,context)) {
+ myNestedResult = null;
+
+ //if (maxOccurs==1 && minOccurs==1) {
+ // if (context.hasResult()) context.getResult().removeSon(name);
+ //}
+ // @todo we may fail fast the match by throwing an exception
+
+ return false;
+ }
+
+ if (!Configuration.CONTEXT_VAR_NAME.equals(name)) addResult(match, start, end, context);
+
+ return true;
+ }
+
+ private MatchResultImpl createMatch(final PsiElement match, int start, int end) {
+ final String image = match == null ? null : StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end);
+ final SmartPsiPointer ref = new SmartPsiPointer(match);
+
+ final MatchResultImpl result = myNestedResult == null ? new MatchResultImpl(
+ name,
+ image,
+ ref,
+ start,
+ end,
+ target
+ ) : myNestedResult;
+
+ if (myNestedResult != null) {
+ myNestedResult.setName( name );
+ myNestedResult.setMatchImage( image );
+ myNestedResult.setMatchRef( ref );
+ myNestedResult.setStart( start );
+ myNestedResult.setEnd( end );
+ myNestedResult.setTarget( target );
+ myNestedResult = null;
+ }
+
+ return result;
+ }
+
+ boolean validate(MatchContext context, Class elementContext) {
+ MatchResult substitution = context.getResult().findSon(name);
+
+ if (minOccurs >= 1 &&
+ ( substitution == null ||
+ StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()).getElementContextByPsi(substitution.getMatchRef().getElement()) != elementContext
+ )
+ ) {
+ return false;
+ } else if (maxOccurs <= 1 &&
+ substitution!=null && substitution.hasSons()
+ ) {
+ return false;
+ } else if (maxOccurs==0 && totalMatchedOccurs!=-1) {
+ return false;
+ }
+ return true;
+ }
+
+ public int getMinOccurs() {
+ return minOccurs;
+ }
+
+ public int getMaxOccurs() {
+ return maxOccurs;
+ }
+
+ private void removeLastResults(int numberOfResults, MatchContext context) {
+ if (numberOfResults == 0) return;
+ final MatchResultImpl substitution = context.getResult().findSon(name);
+
+ if (substitution!=null) {
+ final List<PsiElement> matchedNodes = context.getMatchedNodes();
+
+ if (substitution.hasSons()) {
+ final List<MatchResult> sons = substitution.getMatches();
+
+ while(numberOfResults > 0) {
+ --numberOfResults;
+ final MatchResult matchResult = sons.remove(sons.size() - 1);
+ if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch());
+ }
+
+ if (sons.isEmpty()) {
+ context.getResult().removeSon(name);
+ }
+ } else {
+ final MatchResultImpl matchResult = context.getResult().removeSon(name);
+ if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch());
+ }
+ }
+ }
+
+ public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) {
+ final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null;
+ context.setResult(null);
+
+ try {
+
+ if (patternNodes.hasNext() && !matchedNodes.hasNext()) {
+ return validateSatisfactionOfHandlers(patternNodes, context);
+ }
+
+ Set<PsiElement> matchedElements = null;
+
+ for(; patternNodes.hasNext(); patternNodes.advance()) {
+ int matchedOccurs = 0;
+ final PsiElement patternNode = patternNodes.current();
+ final CompiledPattern pattern = context.getPattern();
+ final MatchingHandler handler = pattern.getHandler(patternNode);
+
+ final PsiElement startMatching = matchedNodes.current();
+ do {
+ final PsiElement element = handler.getPinnedNode(null);
+ final PsiElement matchedNode = (element != null) ? element : matchedNodes.current();
+
+ if (element == null) matchedNodes.advance();
+ if (!matchedNodes.hasNext()) matchedNodes.reset();
+
+ if (matchedOccurs <= maxOccurs &&
+ (matchedElements == null || !matchedElements.contains(matchedNode))) {
+
+ if (handler.match(patternNode, matchedNode, context)) {
+ ++matchedOccurs;
+ if (matchedElements == null) matchedElements = new HashSet<PsiElement>();
+ matchedElements.add(matchedNode);
+ if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) {
+ break;
+ }
+ } else if (element != null) {
+ return false;
+ }
+
+ // clear state of dependent objects
+ clearingVisitor.clearState(pattern, patternNode);
+ }
+
+ // passed of elements and does not found the match
+ if (startMatching == matchedNodes.current()) {
+ final boolean result = validateSatisfactionOfHandlers(patternNodes, context) &&
+ matchedOccurs >= minOccurs && matchedOccurs <= maxOccurs;
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ }
+ } while(true);
+
+ if (!handler.shouldAdvanceThePatternFor(patternNode, null)) {
+ patternNodes.rewind();
+ }
+ }
+
+ final boolean result = validateSatisfactionOfHandlers(patternNodes, context);
+ if (result && context.getMatchedElementsListener() != null) {
+ context.getMatchedElementsListener().matchedElements(matchedElements);
+ }
+ return result;
+ } finally {
+ if (saveResult!=null) {
+ if (context.hasResult()) {
+ saveResult.getMatches().addAll(context.getResult().getMatches());
+ }
+ context.setResult(saveResult);
+ }
+ }
+ }
+
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ return doMatchSequentially(nodes, nodes2, context);
+ }
+
+ protected boolean doMatchSequentiallyBySimpleHandler(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ final boolean oldValue = context.shouldRecursivelyMatch();
+ context.setShouldRecursivelyMatch(false);
+ final boolean result = super.matchSequentially(nodes, nodes2, context);
+ context.setShouldRecursivelyMatch(oldValue);
+ return result;
+ }
+
+ protected boolean doMatchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ final int previousMatchedOccurs = matchedOccurs;
+
+ FilteringNodeIterator fNodes2 = new FilteringNodeIterator(nodes2, VARS_DELIM_FILTER);
+
+ try {
+ MatchingHandler handler = context.getPattern().getHandler(nodes.current());
+ matchedOccurs = 0;
+
+ boolean flag = false;
+
+ while(fNodes2.hasNext() && matchedOccurs < minOccurs) {
+ if (handler.match(nodes.current(), nodes2.current(), context)) {
+ ++matchedOccurs;
+ } else {
+ break;
+ }
+ fNodes2.advance();
+ flag = true;
+ }
+
+ if (matchedOccurs!=minOccurs) {
+ // failed even for min occurs
+ removeLastResults(matchedOccurs, context);
+ fNodes2.rewind(matchedOccurs);
+ return false;
+ }
+
+ if (greedy) {
+ // go greedily to maxOccurs
+
+ while(fNodes2.hasNext() && matchedOccurs < maxOccurs) {
+ if (handler.match(nodes.current(), nodes2.current(), context)) {
+ ++matchedOccurs;
+ } else {
+ // no more matches could take!
+ break;
+ }
+ fNodes2.advance();
+ flag = true;
+ }
+
+ if (flag) {
+ fNodes2.rewind();
+ nodes2.advance();
+ }
+
+ nodes.advance();
+
+ if (nodes.hasNext()) {
+ final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
+
+ while(matchedOccurs >= minOccurs) {
+ if (nextHandler.matchSequentially(nodes, nodes2, context)) {
+ totalMatchedOccurs = matchedOccurs;
+ // match found
+ return true;
+ }
+
+ if (matchedOccurs > 0) {
+ nodes2.rewind();
+ removeLastResults(1,context);
+ }
+ --matchedOccurs;
+ }
+
+ if (matchedOccurs > 0) {
+ removeLastResults(matchedOccurs, context);
+ }
+ nodes.rewind();
+ return false;
+ } else {
+ // match found
+ if (handler.isMatchSequentiallySucceeded(nodes2)) {
+ return checkSameOccurrencesConstraint();
+ }
+ removeLastResults(matchedOccurs, context);
+ return false;
+ }
+ } else {
+ nodes.advance();
+
+ if (flag) {
+ fNodes2.rewind();
+ nodes2.advance();
+ }
+
+ if (nodes.hasNext()) {
+ final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
+
+ flag = false;
+
+ while(nodes2.hasNext() && matchedOccurs <= maxOccurs) {
+ if (nextHandler.matchSequentially(nodes, nodes2, context)) {
+ return checkSameOccurrencesConstraint();
+ }
+
+ if (flag) {
+ nodes2.rewind();
+ fNodes2.advance();
+ }
+
+ if (handler.match(nodes.current(), nodes2.current(), context)) {
+ matchedOccurs++;
+ } else {
+ nodes.rewind();
+ removeLastResults(matchedOccurs,context);
+ return false;
+ }
+ nodes2.advance();
+ flag = true;
+ }
+
+ nodes.rewind();
+ removeLastResults(matchedOccurs,context);
+ return false;
+ } else {
+ return checkSameOccurrencesConstraint();
+ }
+ }
+ } finally {
+ matchedOccurs = previousMatchedOccurs;
+ }
+ }
+
+ private boolean checkSameOccurrencesConstraint() {
+ if (totalMatchedOccurs == -1) {
+ totalMatchedOccurs = matchedOccurs;
+ return true;
+ }
+ else {
+ return totalMatchedOccurs == matchedOccurs;
+ }
+ }
+
+ public void setTarget(boolean target) {
+ this.target = target;
+ }
+
+ public MatchingHandler getMatchHandler() {
+ return matchHandler;
+ }
+
+ public void setMatchHandler(MatchingHandler matchHandler) {
+ this.matchHandler = matchHandler;
+ }
+
+ public boolean isTarget() {
+ return target;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void reset() {
+ super.reset();
+ totalMatchedOccurs = -1;
+ }
+
+ public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) {
+ if(maxOccurs > 1) return false;
+ return super.shouldAdvanceThePatternFor(patternElement,matchedElement);
+ }
+
+ public void setNestedResult(final MatchResultImpl nestedResult) {
+ myNestedResult = nestedResult;
+ }
+
+ public MatchResultImpl getNestedResult() {
+ return myNestedResult;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java
new file mode 100644
index 000000000000..4d1f9a852940
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java
@@ -0,0 +1,21 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Search handler for symbol search
+ */
+public class SymbolHandler extends MatchingHandler {
+ private final SubstitutionHandler handler;
+
+ public SymbolHandler(SubstitutionHandler handler) {
+ this.handler = handler;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ // there is no need to do filtering since this is delegate of Substituion handler
+
+ return handler.handle(matchedNode,context);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java
new file mode 100644
index 000000000000..6711bcb1fc7f
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java
@@ -0,0 +1,85 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.iterators.SiblingNodeIterator;
+import com.intellij.psi.PsiComment;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class TopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler {
+ private final MatchingHandler delegate;
+
+ public TopLevelMatchingHandler(@NotNull MatchingHandler _delegate) {
+ delegate = _delegate;
+ setFilter(_delegate.getFilter());
+ }
+
+ public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) {
+ final boolean matched = delegate.match(patternNode, matchedNode, matchContext);
+
+ if (matched) {
+ List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
+ if (matchedNodes == null) {
+ matchedNodes = new ArrayList<PsiElement>();
+ matchContext.setMatchedNodes(matchedNodes);
+ }
+
+ PsiElement elementToAdd = matchedNode;
+
+ if (patternNode instanceof PsiComment && StructuralSearchUtil.isDocCommentOwner(matchedNode)) {
+ // psicomment and psidoccomment are placed inside the psimember next to them so
+ // simple topdown matching should do additional "dances" to cover this case.
+ elementToAdd = matchedNode.getFirstChild();
+ assert elementToAdd instanceof PsiComment;
+ }
+
+ matchedNodes.add(elementToAdd);
+ }
+
+ if ((!matched || matchContext.getOptions().isRecursiveSearch()) &&
+ matchContext.getPattern().getStrategy().continueMatching(matchedNode) &&
+ matchContext.shouldRecursivelyMatch()
+ ) {
+ matchContext.getMatcher().matchContext(
+ new SsrFilteringNodeIterator(
+ new SiblingNodeIterator(matchedNode.getFirstChild())
+ )
+ );
+ }
+ return matched;
+ }
+
+ @Override
+ public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) {
+ return delegate.canMatch(patternNode, matchedNode);
+ }
+
+ @Override
+ public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) {
+ return delegate.matchSequentially(nodes, nodes2, context);
+ }
+
+ public boolean match(final PsiElement patternNode,
+ final PsiElement matchedNode, final int start, final int end, final MatchContext context) {
+ return match(patternNode, matchedNode, context);
+ }
+
+ public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) {
+ return delegate.shouldAdvanceTheMatchFor(patternElement, matchedElement);
+ }
+
+ public MatchingHandler getDelegate() {
+ return delegate;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java
new file mode 100644
index 000000000000..08e335614d5a
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java
@@ -0,0 +1,21 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiDeclarationStatement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Search handler for typed symbol ('T<a*>)
+ */
+public class TypedSymbolHandler extends MatchingHandler {
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ if (!super.match(patternNode,matchedNode,context)) {
+ return false;
+ }
+
+ return context.getMatcher().match(
+ patternNode.getFirstChild(),
+ matchedNode
+ );
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java
new file mode 100644
index 000000000000..cc71c02ee403
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java
@@ -0,0 +1,23 @@
+package com.intellij.structuralsearch.impl.matcher.handlers;
+
+import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public final class XmlTextHandler extends MatchingHandler {
+ public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
+ final PsiElement psiElement = nodes.current();
+
+ return GlobalMatchingVisitor.continueMatchingSequentially(
+ new SsrFilteringNodeIterator( new ArrayBackedNodeIterator(psiElement.getChildren()) ),
+ nodes2,
+ context
+ );
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java
new file mode 100644
index 000000000000..a2b14ae83e17
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java
@@ -0,0 +1,20 @@
+package com.intellij.structuralsearch.impl.matcher.iterators;
+
+import com.intellij.dupLocator.iterators.FilteringNodeIterator;
+import com.intellij.dupLocator.iterators.NodeIterator;
+import com.intellij.dupLocator.iterators.SiblingNodeIterator;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class SsrFilteringNodeIterator extends FilteringNodeIterator {
+ public SsrFilteringNodeIterator(final NodeIterator iterator) {
+ super(iterator, LexicalNodesFilter.getInstance());
+ }
+
+ public SsrFilteringNodeIterator(final PsiElement element) {
+ this(new SiblingNodeIterator(element));
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java
new file mode 100644
index 000000000000..916fc1e15639
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java
@@ -0,0 +1,22 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class AbstractStringBasedPredicate extends MatchPredicate {
+ protected final String myName;
+ protected final String myWithin;
+
+ public AbstractStringBasedPredicate(String name, String within) {
+ myName = name;
+ myWithin = within;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ return match(patternNode, matchedNode, 0, -1, context);
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java
new file mode 100644
index 000000000000..b7d5432964d4
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java
@@ -0,0 +1,38 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * Binary predicate
+ */
+public final class BinaryPredicate extends MatchPredicate {
+ private final MatchPredicate first;
+ private final MatchPredicate second;
+ private final boolean or;
+
+ public BinaryPredicate(MatchPredicate first, MatchPredicate second, boolean or) {
+ this.first = first;
+ this.second = second;
+ this.or = or;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ if (or) {
+ return first.match(patternNode,matchedNode,context) ||
+ second.match(patternNode,matchedNode,context);
+ } else {
+ return first.match(patternNode,matchedNode,context) &&
+ second.match(patternNode,matchedNode,context);
+ }
+ }
+
+ public MatchPredicate getFirst() {
+ return first;
+ }
+
+ public MatchPredicate getSecond() {
+ return second;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java
new file mode 100644
index 000000000000..2ed894fe6b64
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java
@@ -0,0 +1,18 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class ContainsPredicate extends AbstractStringBasedPredicate {
+
+ public ContainsPredicate(String name, String within) {
+ super(name, within);
+ }
+
+ public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) {
+ return false;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java
new file mode 100644
index 000000000000..3d28a635e3af
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java
@@ -0,0 +1,24 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+
+/**
+ * Negates predicate
+ */
+public final class NotPredicate extends MatchPredicate {
+ private final MatchPredicate handler;
+
+ public NotPredicate(final MatchPredicate _handler) {
+ handler = _handler;
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ return !handler.match(patternNode,matchedNode,context);
+ }
+
+ public MatchPredicate getHandler() {
+ return handler;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java
new file mode 100644
index 000000000000..17570ed8f5df
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java
@@ -0,0 +1,31 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchUtils;
+
+/**
+ * Handles finding method
+ */
+public final class ReferencePredicate extends SubstitutionHandler {
+ public ReferencePredicate(String _name) {
+ super(_name, true, 1, 1, true);
+ }
+
+ public boolean match(PsiElement node, PsiElement match, MatchContext context) {
+ if (StructuralSearchUtil.isIdentifier(match)) {
+ // since we pickup tokens
+ match = match.getParent();
+ }
+
+ PsiElement result = MatchUtils.getReferencedElement(match);
+ if (result == null) {
+ result = match;
+ //return false;
+ }
+
+ return handle(result,context);
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java
new file mode 100644
index 000000000000..f7b8ecedcf3d
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java
@@ -0,0 +1,175 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.*;
+import com.intellij.structuralsearch.MalformedPatternException;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchProfile;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import com.intellij.structuralsearch.impl.matcher.MatchUtils;
+import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
+import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Root of handlers for pattern node matching. Handles simpliest type of the match.
+ */
+public final class RegExpPredicate extends MatchPredicate {
+ private Pattern pattern;
+ private final String baseHandlerName;
+ private boolean simpleString;
+ private final boolean couldBeOptimized;
+ private final String regexp;
+ private final boolean caseSensitive;
+ private boolean multiline;
+ private final boolean wholeWords;
+ private final boolean target;
+ private NodeTextGenerator myNodeTextGenerator;
+
+ public interface NodeTextGenerator {
+ String getText(PsiElement element);
+ }
+
+ public RegExpPredicate(final String regexp, final boolean caseSensitive, final String _baseHandlerName, boolean _wholeWords, boolean _target) {
+ couldBeOptimized = containsRegExp(regexp);
+ if (!_wholeWords) {
+ simpleString = couldBeOptimized;
+ }
+
+ this.regexp = regexp;
+ this.caseSensitive = caseSensitive;
+ this.wholeWords = _wholeWords;
+ baseHandlerName = _baseHandlerName;
+
+ if (!simpleString) {
+ compilePattern();
+ }
+ target = _target;
+ }
+
+ private static boolean containsRegExp(final String regexp) {
+ for(int i=0;i<regexp.length();++i) {
+ if(MatchUtils.SPECIAL_CHARS.indexOf(regexp.charAt(i))!=-1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void compilePattern() {
+ try {
+ @NonNls String realRegexp = regexp;
+ if (wholeWords) {
+ realRegexp = ".*?\\b(?:" + realRegexp + ")\\b.*?";
+ }
+
+ pattern = Pattern.compile(
+ realRegexp,
+ (caseSensitive ? 0: Pattern.CASE_INSENSITIVE) | (multiline ? Pattern.DOTALL:0)
+ );
+ } catch(PatternSyntaxException ex) {
+ throw new MalformedPatternException(SSRBundle.message("error.incorrect.regexp.constraint", regexp, baseHandlerName));
+ }
+ }
+
+ public boolean couldBeOptimized() {
+ return couldBeOptimized;
+ }
+
+ public String getRegExp() {
+ return regexp;
+ }
+
+ /**
+ * Attempts to match given handler node against given node.
+ * @param matchedNode for matching
+ * @param context of the matching
+ * @return true if matching was successfull and false otherwise
+ */
+ public boolean match(PsiElement node,PsiElement matchedNode, int start, int end, MatchContext context) {
+ if (matchedNode==null) return false;
+ String text;
+
+ text = myNodeTextGenerator != null ? myNodeTextGenerator.getText(matchedNode) : getMeaningfulText(matchedNode);
+
+ boolean result = doMatch(text, start, end, context, matchedNode);
+
+ if (!result) {
+
+ if(StructuralSearchUtil.isIdentifier(matchedNode)) {
+ matchedNode = matchedNode.getParent();
+ }
+
+ String alternativeText = context.getPattern().getAlternativeTextToMatch(matchedNode, text);
+ if (alternativeText != null) {
+ result = doMatch(alternativeText, start, end, context, matchedNode);
+ }
+ }
+
+ return result;
+ }
+
+ public static String getMeaningfulText(PsiElement matchedNode) {
+ final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(matchedNode);
+ return profile != null ? profile.getMeaningfulText(matchedNode) : matchedNode.getText();
+ }
+
+ public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) {
+ return match(patternNode,matchedNode,0,-1,context);
+ }
+
+ boolean doMatch(String text, MatchContext context, PsiElement matchedElement) {
+ return doMatch(text,0,-1,context, matchedElement);
+ }
+
+ boolean doMatch(String text, int from, int end, MatchContext context,PsiElement matchedElement) {
+ if (from > 0 || end != -1) {
+ text = text.substring(from, end == -1 || end >= text.length() ? text.length():end);
+ }
+
+ if (simpleString) {
+ return (caseSensitive)?text.equals(regexp):text.equalsIgnoreCase(regexp);
+ }
+
+ if(!multiline && text.contains("\n")) setMultiline(true);
+ final Matcher matcher = pattern.matcher(text);
+
+ if (matcher.matches()) {
+ for (int i=1;i<=matcher.groupCount();++i) {
+ context.getResult().addSon(
+ new MatchResultImpl(
+ baseHandlerName + "_" + i,
+ matcher.group(i),
+ new SmartPsiPointer(matchedElement),
+ matcher.start(i),
+ matcher.end(i),
+ target
+ )
+ );
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ public void setNodeTextGenerator(final NodeTextGenerator nodeTextGenerator) {
+ myNodeTextGenerator = nodeTextGenerator;
+ }
+
+ public void setMultiline(boolean b) {
+ multiline = b;
+ compilePattern();
+ }
+
+ public boolean isWholeWords() {
+ return wholeWords;
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java
new file mode 100644
index 000000000000..9310eb9e70a0
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java
@@ -0,0 +1,28 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class ScriptPredicate extends AbstractStringBasedPredicate {
+ private final ScriptSupport scriptSupport;
+
+ public ScriptPredicate(String name, String within) {
+ super(name, within);
+ scriptSupport = new ScriptSupport(within, name);
+ }
+
+ public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) {
+ if (match == null) return false;
+
+ return Boolean.TRUE.equals(
+ Boolean.valueOf(scriptSupport.evaluate(
+ context.hasResult() ? context.getResult() : null,
+ match
+ ))
+ );
+ }
+
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java
new file mode 100644
index 000000000000..e4136ede2831
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java
@@ -0,0 +1,91 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.SSRBundle;
+import com.intellij.structuralsearch.StructuralSearchException;
+import com.intellij.structuralsearch.StructuralSearchUtil;
+import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
+import groovy.lang.Binding;
+import groovy.lang.GroovyRuntimeException;
+import groovy.lang.GroovyShell;
+import groovy.lang.Script;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.ErrorCollector;
+import org.codehaus.groovy.control.MultipleCompilationErrorsException;
+import org.codehaus.groovy.control.messages.Message;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Maxim.Mossienko
+ * Date: 11.06.2009
+ * Time: 16:25:12
+ */
+public class ScriptSupport {
+ private final Script script;
+
+ public ScriptSupport(String text, String name) {
+ File scriptFile = new File(text);
+ GroovyShell shell = new GroovyShell();
+ try {
+ script = scriptFile.exists() ? shell.parse(scriptFile):shell.parse(text, name);
+ } catch (Exception ex) {
+ Logger.getInstance(getClass().getName()).error(ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public String evaluate(MatchResultImpl result, PsiElement context) {
+ try {
+ Binding binding = new Binding();
+
+ if (result != null) {
+ for(MatchResult r:result.getMatches()) {
+ binding.setVariable(r.getName(),r.getMatchRef().getElement());
+ }
+ }
+
+ if (context == null) {
+ context = result.getMatchRef().getElement();
+ }
+ if (StructuralSearchUtil.isIdentifier(context)) context = context.getParent();
+ binding.setVariable("__context__", context);
+ script.setBinding(binding);
+
+ Object o = script.run();
+ return String.valueOf(o);
+ } catch (GroovyRuntimeException ex) {
+ throw new StructuralSearchException(SSRBundle.message("groovy.script.error", ex.getMessage()));
+ }
+ }
+
+ public static String checkValidScript(String scriptText) {
+ try {
+ final File scriptFile = new File(scriptText);
+ final GroovyShell shell = new GroovyShell();
+ final Script script = scriptFile.exists() ? shell.parse(scriptFile) : shell.parse(scriptText);
+ return null;
+ } catch (IOException e) {
+ return e.getMessage();
+ } catch (MultipleCompilationErrorsException e) {
+ final ErrorCollector errorCollector = e.getErrorCollector();
+ final List<Message> errors = errorCollector.getErrors();
+ for (Message error : errors) {
+ if (error instanceof SyntaxErrorMessage) {
+ final SyntaxErrorMessage errorMessage = (SyntaxErrorMessage)error;
+ final SyntaxException cause = errorMessage.getCause();
+ return cause.getMessage();
+ }
+ }
+ return e.getMessage();
+ } catch (CompilationFailedException ex) {
+ return ex.getLocalizedMessage();
+ }
+ }
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java
new file mode 100644
index 000000000000..3904eb397bce
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java
@@ -0,0 +1,38 @@
+package com.intellij.structuralsearch.impl.matcher.predicates;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiElement;
+import com.intellij.structuralsearch.MatchOptions;
+import com.intellij.structuralsearch.MatchResult;
+import com.intellij.structuralsearch.Matcher;
+import com.intellij.structuralsearch.impl.matcher.MatchContext;
+import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler;
+
+/**
+ * @author Maxim.Mossienko
+ */
+public class WithinPredicate extends AbstractStringBasedPredicate {
+ private final MatchOptions myMatchOptions;
+ private Matcher matcher;
+
+ public WithinPredicate(String name, String within, Project project) {
+ super(name, within);
+ myMatchOptions = new MatchOptions();
+
+ myMatchOptions.setLooseMatching(true);
+ final String unquoted = StringUtil.stripQuotesAroundValue(within);
+ if (!unquoted.equals(within)) {
+ myMatchOptions.setSearchPattern(unquoted);
+ PatternCompiler.transformOldPattern(myMatchOptions);
+ matcher = new Matcher(project, myMatchOptions);
+ } else {
+ assert false;
+ }
+ }
+
+ public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) {
+ final MatchResult result = matcher.isMatchedByDownUp(match, myMatchOptions);
+ return result != null;
+ }
+} \ No newline at end of file
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java
new file mode 100644
index 000000000000..8f5b5bc2fc20
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java
@@ -0,0 +1,13 @@
+package com.intellij.structuralsearch.impl.matcher.strategies;
+
+import com.intellij.psi.PsiElement;
+
+
+/**
+ * CommonStrategy of metching process
+ */
+public interface MatchingStrategy {
+ boolean continueMatching(PsiElement start);
+
+ boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith);
+}
diff --git a/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java
new file mode 100644
index 000000000000..b2ca87a176e8
--- /dev/null
+++ b/platform/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java
@@ -0,0 +1,42 @@
+package com.intellij.structuralsearch.impl.matcher.strategies;
+
+import com.intellij.dupLocator.util.NodeFilter;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.XmlElementVisitor;
+import com.intellij.psi.xml.XmlTag;
+
+/**
+ * Base filtering strategy to find statements
+ */
+public class XmlMatchingStrategy extends XmlElementVisitor implements MatchingStrategy,NodeFilter {
+ protected boolean result;
+
+ @Override public void visitXmlTag(final XmlTag element) {
+ result = true;
+ }
+
+ public boolean continueMatching(final PsiElement start) {
+ return accepts(start);
+ }
+
+ @Override
+ public boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) {
+ return false;
+ }
+
+ protected XmlMatchingStrategy() {}
+
+ private static class XmlMatchingStrategyHolder {
+ private static final XmlMatchingStrategy instance = new XmlMatchingStrategy();
+ }
+
+ public static MatchingStrategy getInstance() {
+ return XmlMatchingStrategyHolder.instance;
+ }
+
+ public boolean accepts(PsiElement element) {
+ result = false;
+ if (element!=null) element.accept(this);
+ return result;
+ }
+}