SSR: cleanup MatchResultImpl & related
[idea/community.git] / platform / structuralsearch / source / com / intellij / structuralsearch / impl / matcher / GlobalMatchingVisitor.java
1 package com.intellij.structuralsearch.impl.matcher;
2
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.openapi.util.Key;
9 import com.intellij.psi.PsiElement;
10 import com.intellij.psi.PsiElementVisitor;
11 import com.intellij.structuralsearch.MatchResult;
12 import com.intellij.structuralsearch.StructuralSearchProfile;
13 import com.intellij.structuralsearch.StructuralSearchUtil;
14 import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
15 import com.intellij.structuralsearch.impl.matcher.handlers.DelegatingHandler;
16 import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
17 import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
18 import com.intellij.structuralsearch.plugin.ui.Configuration;
19 import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
20 import com.intellij.util.containers.HashMap;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
23
24 import java.util.List;
25 import java.util.Map;
26
27 /**
28  * Visitor class to manage pattern matching
29  */
30 @SuppressWarnings({"RefusedBequest"})
31 public class GlobalMatchingVisitor extends AbstractMatchingVisitor {
32   private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor");
33   public static final Key<List<PsiElement>> UNMATCHED_ELEMENTS_KEY = Key.create("UnmatchedElements");
34
35   // the pattern element for visitor check
36   private PsiElement myElement;
37
38   // the result of matching in visitor
39   private boolean myResult;
40
41   // context of matching
42   private MatchContext matchContext;
43
44   private final Map<Language, PsiElementVisitor> myLanguage2MatchingVisitor = new HashMap<Language, PsiElementVisitor>(1);
45
46   public PsiElement getElement() {
47     return myElement;
48   }
49
50   public boolean getResult() {
51     return myResult;
52   }
53
54   public void setResult(boolean result) {
55     this.myResult = result;
56   }
57
58   public MatchContext getMatchContext() {
59     return matchContext;
60   }
61
62   @Override
63   protected boolean doMatchInAnyOrder(NodeIterator elements, NodeIterator elements2) {
64     return matchContext.getPattern().getHandler(elements.current()).matchInAnyOrder(
65       elements,
66       elements2,
67       matchContext
68     );
69   }
70
71   @NotNull
72   @Override
73   protected NodeFilter getNodeFilter() {
74     return LexicalNodesFilter.getInstance();
75   }
76
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();
82     }
83     assert handler instanceof SubstitutionHandler :
84       handler != null ? handler.getClass() : "null" + ' ' + (initialHandler != null ? initialHandler.getClass() : "null");
85
86     return ((SubstitutionHandler)handler).handle(match, matchContext);
87   }
88
89   public boolean allowsAbsenceOfMatch(final PsiElement element) {
90     final MatchingHandler handler = getMatchContext().getPattern().getHandler(element);
91     return handler instanceof SubstitutionHandler && ((SubstitutionHandler)handler).getMinOccurs() == 0;
92   }
93
94   /**
95    * Identifies the match between given element of program tree and pattern element
96    *
97    * @param el1 the pattern for matching
98    * @param el2 the tree element for matching
99    * @return true if equal and false otherwise
100    */
101   public boolean match(final PsiElement el1, final PsiElement el2) {
102     if (el1 == el2) return true;
103     if (el1 == null) {
104       // absence of pattern element is match
105       return true;
106     }
107     if (el2 == null) {
108       // absence of match element needs check if allowed.
109       return allowsAbsenceOfMatch(el1);
110     }
111
112     // copy changed data to local stack
113     PsiElement prevElement = myElement;
114     myElement = el2;
115
116     try {
117       PsiElementVisitor visitor = getVisitorForElement(el1);
118       if (visitor != null) {
119         el1.accept(visitor);
120       }
121     }
122     catch (ClassCastException ex) {
123       myResult = false;
124     }
125     finally {
126       myElement = prevElement;
127     }
128
129     return myResult;
130   }
131
132   @Nullable
133   private PsiElementVisitor getVisitorForElement(PsiElement element) {
134     Language language = element.getLanguage();
135     PsiElementVisitor visitor = myLanguage2MatchingVisitor.get(language);
136     if (visitor == null) {
137       visitor = createMatchingVisitor(language);
138       myLanguage2MatchingVisitor.put(language, visitor);
139     }
140     return visitor;
141   }
142
143   @Nullable
144   private PsiElementVisitor createMatchingVisitor(Language language) {
145     StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language);
146     if (profile == null) {
147       LOG.warn("there is no StructuralSearchProfile for language " + language.getID());
148       return null;
149     }
150     else {
151       return profile.createMatchingVisitor(this);
152     }
153   }
154
155   /**
156    * Matches tree segments starting with given elements to find equality
157    *
158    * @param nodes the pattern element for matching
159    * @param nodes2 the tree element for matching
160    * @return if they are equal and false otherwise
161    */
162   public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) {
163     if (!nodes.hasNext()) {
164       return !nodes2.hasNext();
165     }
166
167     return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(
168       nodes,
169       nodes2,
170       matchContext
171     );
172   }
173
174   public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) {
175     if (!nodes.hasNext()) {
176       return !nodes2.hasNext();
177     }
178
179     return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(
180       nodes,
181       nodes2,
182       matchContext
183     );
184   }
185
186   /**
187    * Descents the tree in depth finding matches
188    *
189    * @param elements the element for which the sons are looked for match
190    */
191   public void matchContext(final NodeIterator elements) {
192     if (matchContext == null) {
193       return;
194     }
195     final CompiledPattern pattern = matchContext.getPattern();
196     final NodeIterator patternNodes = pattern.getNodes().clone();
197     final MatchResultImpl saveResult = matchContext.hasResult() ? matchContext.getResult() : null;
198     final List<PsiElement> saveMatchedNodes = matchContext.getMatchedNodes();
199
200     try {
201       matchContext.setResult(null);
202       matchContext.setMatchedNodes(null);
203
204       if (!patternNodes.hasNext()) return;
205       final MatchingHandler firstMatchingHandler = pattern.getHandler(patternNodes.current());
206
207       for (; elements.hasNext(); elements.advance()) {
208         final PsiElement elementNode = elements.current();
209
210         boolean matched = firstMatchingHandler.matchSequentially(patternNodes, elements, matchContext);
211
212         if (matched) {
213           MatchingHandler matchingHandler = matchContext.getPattern().getHandler(Configuration.CONTEXT_VAR_NAME);
214           if (matchingHandler != null) {
215             matched = ((SubstitutionHandler)matchingHandler).handle(elementNode, matchContext);
216           }
217         }
218
219         final List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
220
221         if (matched) {
222           dispatchMatched(matchedNodes, matchContext.getResult());
223         }
224
225         matchContext.setMatchedNodes(null);
226         matchContext.setResult(null);
227
228         patternNodes.reset();
229         if (matchedNodes != null && matchedNodes.size() > 0 && matched) {
230           elements.rewind();
231         }
232       }
233     }
234     finally {
235       matchContext.setResult(saveResult);
236       matchContext.setMatchedNodes(saveMatchedNodes);
237     }
238   }
239
240   private void dispatchMatched(final List<PsiElement> matchedNodes, MatchResultImpl result) {
241     if (!matchContext.getOptions().isResultIsContextMatch() && doDispatch(result, result)) return;
242
243     // There is no substitutions so show the context
244
245     processNoSubstitutionMatch(matchedNodes, result);
246     matchContext.getSink().newMatch(result);
247   }
248
249   private boolean doDispatch(final MatchResult result, MatchResultImpl context) {
250     boolean ret = false;
251
252     for (MatchResult r : result.getAllSons()) {
253       if ((r.isScopeMatch() && !r.isTarget()) || r.isMultipleMatch()) {
254         ret |= doDispatch(r, context);
255       }
256       else if (r.isTarget()) {
257         ((MatchResultImpl)r).setContext(context);
258         matchContext.getSink().newMatch(r);
259         ret = true;
260       }
261     }
262     return ret;
263   }
264
265   private static void processNoSubstitutionMatch(List<PsiElement> matchedNodes, MatchResultImpl result) {
266     boolean complexMatch = matchedNodes.size() > 1;
267     final PsiElement match = matchedNodes.get(0);
268
269     if (!complexMatch) {
270       result.setMatchRef(new SmartPsiPointer(match));
271       result.setMatchImage(match.getText());
272     }
273     else {
274
275       for (final PsiElement matchStatement : matchedNodes) {
276         result.getMatches().add(new MatchResultImpl(
277             MatchResult.LINE_MATCH,
278             matchStatement.getText(),
279             new SmartPsiPointer(matchStatement),
280             true
281           )
282         );
283       }
284
285       result.setMatchRef(
286         new SmartPsiPointer(match)
287       );
288       result.setMatchImage(
289         match.getText()
290       );
291       result.setName(MatchResult.MULTI_LINE_MATCH);
292     }
293   }
294
295   public void setMatchContext(MatchContext matchContext) {
296     this.matchContext = matchContext;
297   }
298
299   @Override
300   protected boolean isLeftLooseMatching() {
301     return matchContext.getOptions().isLooseMatching();
302   }
303
304   @Override
305   protected boolean isRightLooseMatching() {
306     return false;
307   }
308
309   public boolean matchText(@Nullable PsiElement left, @Nullable PsiElement right) {
310     if (left == null) {
311       return right == null;
312     }
313     else if (right == null) {
314       return false;
315     }
316     final boolean caseSensitiveMatch = matchContext.getOptions().isCaseSensitiveMatch();
317     final String leftText = left.getText();
318     final String rightText = right.getText();
319     return caseSensitiveMatch ? leftText.equals(rightText) : leftText.equalsIgnoreCase(rightText);
320   }
321 }