1 package com.intellij.structuralsearch.impl.matcher;
3 import com.intellij.dupLocator.AbstractMatchingVisitor;
4 import com.intellij.dupLocator.iterators.NodeIterator;
5 import com.intellij.dupLocator.util.NodeFilter;
6 import com.intellij.lang.Language;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.psi.PsiElement;
9 import com.intellij.psi.PsiElementVisitor;
10 import com.intellij.structuralsearch.MatchResult;
11 import com.intellij.structuralsearch.StructuralSearchProfile;
12 import com.intellij.structuralsearch.StructuralSearchUtil;
13 import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
14 import com.intellij.structuralsearch.impl.matcher.handlers.DelegatingHandler;
15 import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
16 import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
17 import com.intellij.structuralsearch.plugin.ui.Configuration;
18 import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
19 import com.intellij.util.containers.HashMap;
20 import org.jetbrains.annotations.NotNull;
21 import org.jetbrains.annotations.Nullable;
23 import java.util.List;
27 * Visitor class to manage pattern matching
29 @SuppressWarnings({"RefusedBequest"})
30 public class GlobalMatchingVisitor extends AbstractMatchingVisitor {
31 private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor");
33 // the pattern element for visitor check
34 private PsiElement myElement;
36 // the result of matching in visitor
37 private boolean myResult;
39 // context of matching
40 private MatchContext matchContext;
42 private MatchingHandler myLastHandler;
44 private Map<Language, PsiElementVisitor> myLanguage2MatchingVisitor = new HashMap<Language, PsiElementVisitor>(1);
46 public PsiElement getElement() {
50 public boolean getResult() {
54 public void setResult(boolean result) {
55 this.myResult = result;
58 public MatchContext getMatchContext() {
63 protected boolean doMatchInAnyOrder(NodeIterator elements, NodeIterator elements2) {
64 return matchContext.getPattern().getHandler(elements.current()).matchInAnyOrder(
73 protected NodeFilter getNodeFilter() {
74 return LexicalNodesFilter.getInstance();
77 public final boolean handleTypedElement(final PsiElement typedElement, final PsiElement match) {
78 MatchingHandler handler = matchContext.getPattern().getHandler(typedElement);
79 final MatchingHandler initialHandler = handler;
80 if (handler instanceof DelegatingHandler) {
81 handler = ((DelegatingHandler)handler).getDelegate();
83 assert handler instanceof SubstitutionHandler :
84 handler != null ? handler.getClass() : "null" + ' ' + (initialHandler != null ? initialHandler.getClass() : "null");
86 return ((SubstitutionHandler)handler).handle(match, matchContext);
90 * Identifies the match between given element of program tree and pattern element
92 * @param el1 the pattern for matching
93 * @param el2 the tree element for matching
94 * @return true if equal and false otherwise
96 public boolean match(final PsiElement el1, final PsiElement el2) {
98 if (el1 == el2) return true;
99 if (el2 == null || el1 == null) {
104 // copy changed data to local stack
105 PsiElement prevElement = myElement;
109 /*if (el1 instanceof XmlElement) {
110 el1.accept(myXmlVisitor);
113 el1.accept(myJavaVisitor);
115 PsiElementVisitor visitor = getVisitorForElement(el1);
116 if (visitor != null) {
120 catch (ClassCastException ex) {
124 myElement = prevElement;
131 private PsiElementVisitor getVisitorForElement(PsiElement element) {
132 Language language = element.getLanguage();
133 PsiElementVisitor visitor = myLanguage2MatchingVisitor.get(language);
134 if (visitor == null) {
135 visitor = createMatchingVisitor(language);
136 myLanguage2MatchingVisitor.put(language, visitor);
142 private PsiElementVisitor createMatchingVisitor(Language language) {
143 StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
144 if (profile == null) {
145 LOG.warn("there is no StructuralSearchProfile for language " + language.getID());
149 return profile.createMatchingVisitor(this);
154 * Matches tree segments starting with given elements to find equality
156 * @param nodes the pattern element for matching
157 * @param nodes2 the tree element for matching
158 * @return if they are equal and false otherwise
160 public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) {
161 if (!nodes.hasNext()) {
162 return nodes.hasNext() == nodes2.hasNext();
165 myLastHandler = matchContext.getPattern().getHandler(nodes.current());
166 return myLastHandler.matchSequentially(
173 public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) {
174 if (!nodes.hasNext()) {
175 return nodes.hasNext() == nodes2.hasNext();
178 return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(
186 * Descents the tree in depth finding matches
188 * @param elements the element for which the sons are looked for match
190 public void matchContext(final NodeIterator elements) {
191 if (matchContext == null) {
194 final CompiledPattern pattern = matchContext.getPattern();
195 final NodeIterator patternNodes = pattern.getNodes().clone();
196 final MatchResultImpl saveResult = matchContext.hasResult() ? matchContext.getResult() : null;
197 final List<PsiElement> saveMatchedNodes = matchContext.getMatchedNodes();
200 matchContext.setResult(null);
201 matchContext.setMatchedNodes(null);
203 if (!patternNodes.hasNext()) return;
204 final MatchingHandler firstMatchingHandler = pattern.getHandler(patternNodes.current());
206 for (; elements.hasNext(); elements.advance()) {
207 final PsiElement elementNode = elements.current();
209 boolean matched = firstMatchingHandler.matchSequentially(patternNodes, elements, matchContext);
212 MatchingHandler matchingHandler = matchContext.getPattern().getHandler(Configuration.CONTEXT_VAR_NAME);
213 if (matchingHandler != null) {
214 matched = ((SubstitutionHandler)matchingHandler).handle(elementNode, matchContext);
218 final List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
221 dispatchMatched(matchedNodes, matchContext.getResult());
224 matchContext.setMatchedNodes(null);
225 matchContext.setResult(null);
227 patternNodes.reset();
228 if (matchedNodes != null && matchedNodes.size() > 0 && matched) {
234 matchContext.setResult(saveResult);
235 matchContext.setMatchedNodes(saveMatchedNodes);
239 private void dispatchMatched(final List<PsiElement> matchedNodes, MatchResultImpl result) {
240 if (!matchContext.getOptions().isResultIsContextMatch() && doDispatch(result, result)) return;
242 // There is no substitutions so show the context
244 processNoSubstitutionMatch(matchedNodes, result);
245 matchContext.getSink().newMatch(result);
248 private boolean doDispatch(final MatchResultImpl result, MatchResultImpl context) {
251 for (MatchResult _r : result.getAllSons()) {
252 final MatchResultImpl r = (MatchResultImpl)_r;
254 if ((r.isScopeMatch() && !r.isTarget()) || r.isMultipleMatch()) {
255 ret |= doDispatch(r, context);
257 else if (r.isTarget()) {
258 r.setContext(context);
259 matchContext.getSink().newMatch(r);
266 private static void processNoSubstitutionMatch(List<PsiElement> matchedNodes, MatchResultImpl result) {
267 boolean complexMatch = matchedNodes.size() > 1;
268 final PsiElement match = matchedNodes.get(0);
271 result.setMatchRef(new SmartPsiPointer(match));
272 result.setMatchImage(match.getText());
275 MatchResultImpl sonresult;
277 for (final PsiElement matchStatement : matchedNodes) {
278 result.getMatches().add(
279 sonresult = new MatchResultImpl(
280 MatchResult.LINE_MATCH,
281 matchStatement.getText(),
282 new SmartPsiPointer(matchStatement),
287 sonresult.setParent(result);
291 new SmartPsiPointer(match)
293 result.setMatchImage(
296 result.setName(MatchResult.MULTI_LINE_MATCH);
300 public void setMatchContext(MatchContext matchContext) {
301 this.matchContext = matchContext;
304 // Matches the sons of given elements to find equality
305 // @param el1 the pattern element for matching
306 // @param el2 the tree element for matching
307 // @return if they are equal and false otherwise
310 protected boolean isLeftLooseMatching() {
311 return matchContext.getOptions().isLooseMatching();
315 protected boolean isRightLooseMatching() {