EA-61477 (assert: SharedImplUtil.findFileElement)
[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.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;
22
23 import java.util.List;
24 import java.util.Map;
25
26 /**
27  * Visitor class to manage pattern matching
28  */
29 @SuppressWarnings({"RefusedBequest"})
30 public class GlobalMatchingVisitor extends AbstractMatchingVisitor {
31   private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor");
32
33   // the pattern element for visitor check
34   private PsiElement myElement;
35
36   // the result of matching in visitor
37   private boolean myResult;
38
39   // context of matching
40   private MatchContext matchContext;
41
42   private MatchingHandler myLastHandler;
43
44   private 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   /**
90    * Identifies the match between given element of program tree and pattern element
91    *
92    * @param el1 the pattern for matching
93    * @param el2 the tree element for matching
94    * @return true if equal and false otherwise
95    */
96   public boolean match(final PsiElement el1, final PsiElement el2) {
97     // null
98     if (el1 == el2) return true;
99     if (el2 == null || el1 == null) {
100       // this a bug!
101       return false;
102     }
103
104     // copy changed data to local stack
105     PsiElement prevElement = myElement;
106     myElement = el2;
107
108     try {
109       /*if (el1 instanceof XmlElement) {
110         el1.accept(myXmlVisitor);
111       }
112       else {
113         el1.accept(myJavaVisitor);
114       }*/
115       PsiElementVisitor visitor = getVisitorForElement(el1);
116       if (visitor != null) {
117         el1.accept(visitor);
118       }
119     }
120     catch (ClassCastException ex) {
121       myResult = false;
122     }
123     finally {
124       myElement = prevElement;
125     }
126
127     return myResult;
128   }
129
130   @Nullable
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);
137     }
138     return visitor;
139   }
140
141   @Nullable
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());
146       return null;
147     }
148     else {
149       return profile.createMatchingVisitor(this);
150     }
151   }
152
153   /**
154    * Matches tree segments starting with given elements to find equality
155    *
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
159    */
160   public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) {
161     if (!nodes.hasNext()) {
162       return nodes.hasNext() == nodes2.hasNext();
163     }
164
165     myLastHandler = matchContext.getPattern().getHandler(nodes.current());
166     return myLastHandler.matchSequentially(
167       nodes,
168       nodes2,
169       matchContext
170     );
171   }
172
173   public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) {
174     if (!nodes.hasNext()) {
175       return nodes.hasNext() == nodes2.hasNext();
176     }
177
178     return matchContext.getPattern().getHandler(nodes.current()).matchSequentially(
179       nodes,
180       nodes2,
181       matchContext
182     );
183   }
184
185   /**
186    * Descents the tree in depth finding matches
187    *
188    * @param elements the element for which the sons are looked for match
189    */
190   public void matchContext(final NodeIterator elements) {
191     if (matchContext == null) {
192       return;
193     }
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();
198
199     try {
200       matchContext.setResult(null);
201       matchContext.setMatchedNodes(null);
202
203       if (!patternNodes.hasNext()) return;
204       final MatchingHandler firstMatchingHandler = pattern.getHandler(patternNodes.current());
205
206       for (; elements.hasNext(); elements.advance()) {
207         final PsiElement elementNode = elements.current();
208
209         boolean matched = firstMatchingHandler.matchSequentially(patternNodes, elements, matchContext);
210
211         if (matched) {
212           MatchingHandler matchingHandler = matchContext.getPattern().getHandler(Configuration.CONTEXT_VAR_NAME);
213           if (matchingHandler != null) {
214             matched = ((SubstitutionHandler)matchingHandler).handle(elementNode, matchContext);
215           }
216         }
217
218         final List<PsiElement> matchedNodes = matchContext.getMatchedNodes();
219
220         if (matched) {
221           dispatchMatched(matchedNodes, matchContext.getResult());
222         }
223
224         matchContext.setMatchedNodes(null);
225         matchContext.setResult(null);
226
227         patternNodes.reset();
228         if (matchedNodes != null && matchedNodes.size() > 0 && matched) {
229           elements.rewind();
230         }
231       }
232     }
233     finally {
234       matchContext.setResult(saveResult);
235       matchContext.setMatchedNodes(saveMatchedNodes);
236     }
237   }
238
239   private void dispatchMatched(final List<PsiElement> matchedNodes, MatchResultImpl result) {
240     if (!matchContext.getOptions().isResultIsContextMatch() && doDispatch(result, result)) return;
241
242     // There is no substitutions so show the context
243
244     processNoSubstitutionMatch(matchedNodes, result);
245     matchContext.getSink().newMatch(result);
246   }
247
248   private boolean doDispatch(final MatchResultImpl result, MatchResultImpl context) {
249     boolean ret = false;
250
251     for (MatchResult _r : result.getAllSons()) {
252       final MatchResultImpl r = (MatchResultImpl)_r;
253
254       if ((r.isScopeMatch() && !r.isTarget()) || r.isMultipleMatch()) {
255         ret |= doDispatch(r, context);
256       }
257       else if (r.isTarget()) {
258         r.setContext(context);
259         matchContext.getSink().newMatch(r);
260         ret = true;
261       }
262     }
263     return ret;
264   }
265
266   private static void processNoSubstitutionMatch(List<PsiElement> matchedNodes, MatchResultImpl result) {
267     boolean complexMatch = matchedNodes.size() > 1;
268     final PsiElement match = matchedNodes.get(0);
269
270     if (!complexMatch) {
271       result.setMatchRef(new SmartPsiPointer(match));
272       result.setMatchImage(match.getText());
273     }
274     else {
275       MatchResultImpl sonresult;
276
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),
283             true
284           )
285         );
286
287         sonresult.setParent(result);
288       }
289
290       result.setMatchRef(
291         new SmartPsiPointer(match)
292       );
293       result.setMatchImage(
294         match.getText()
295       );
296       result.setName(MatchResult.MULTI_LINE_MATCH);
297     }
298   }
299
300   public void setMatchContext(MatchContext matchContext) {
301     this.matchContext = matchContext;
302   }
303
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
308
309   @Override
310   protected boolean isLeftLooseMatching() {
311     return matchContext.getOptions().isLooseMatching();
312   }
313
314   @Override
315   protected boolean isRightLooseMatching() {
316     return false;
317   }
318 }