SSR: cleanup MatchResultImpl & related
[idea/community.git] / platform / structuralsearch / source / com / intellij / structuralsearch / impl / matcher / handlers / SubstitutionHandler.java
1 package com.intellij.structuralsearch.impl.matcher.handlers;
2
3 import com.intellij.dupLocator.iterators.FilteringNodeIterator;
4 import com.intellij.dupLocator.iterators.NodeIterator;
5 import com.intellij.dupLocator.util.NodeFilter;
6 import com.intellij.psi.PsiElement;
7 import com.intellij.structuralsearch.MatchResult;
8 import com.intellij.structuralsearch.StructuralSearchProfile;
9 import com.intellij.structuralsearch.StructuralSearchUtil;
10 import com.intellij.structuralsearch.impl.matcher.CompiledPattern;
11 import com.intellij.structuralsearch.impl.matcher.MatchContext;
12 import com.intellij.structuralsearch.impl.matcher.MatchResultImpl;
13 import com.intellij.structuralsearch.plugin.ui.Configuration;
14 import com.intellij.structuralsearch.plugin.util.SmartPsiPointer;
15
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Set;
19
20 /**
21  * Matching handler that manages substitutions matching
22  */
23 public class SubstitutionHandler extends MatchingHandler {
24   private final String name;
25   private final int maxOccurs;
26   private final int minOccurs;
27   private final boolean greedy;
28   private boolean target;
29   private MatchPredicate predicate;
30   private MatchingHandler matchHandler;
31   private boolean subtype;
32   private boolean strictSubtype;
33   // matchedOccurs + 1 = number of item being matched
34   private int matchedOccurs;
35   private int totalMatchedOccurs = -1;
36   private MatchResultImpl myNestedResult;
37
38   private static final NodeFilter VARS_DELIM_FILTER = new NodeFilter() {
39     @Override
40     public boolean accepts(PsiElement element) {
41       if (element == null) {
42         return false;
43       }
44
45       final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element);
46       if (profile == null) {
47         return false;
48       }
49
50       return profile.canBeVarDelimeter(element);
51     }
52   };
53
54   public SubstitutionHandler(final String name, final boolean target, int minOccurs,
55                              int maxOccurs, boolean greedy) {
56     this.name = name;
57     this.maxOccurs = maxOccurs;
58     this.minOccurs = minOccurs;
59     this.target = target;
60     this.greedy = greedy;
61   }
62
63   public SubstitutionHandler(final SubstitutionHandler substitutionHandler) {
64     this(substitutionHandler.getName(),substitutionHandler.isTarget(), substitutionHandler.getMinOccurs(),
65          substitutionHandler.getMaxOccurs(), substitutionHandler.greedy);
66   }
67
68   public boolean isSubtype() {
69     return subtype;
70   }
71
72   public boolean isStrictSubtype() {
73     return strictSubtype;
74   }
75
76   public void setStrictSubtype(boolean strictSubtype) {
77     this.strictSubtype = strictSubtype;
78   }
79
80   public void setSubtype(boolean subtype) {
81     this.subtype = subtype;
82   }
83
84   public void setPredicate(MatchPredicate handler) {
85     predicate = handler;
86   }
87
88   // Matcher
89
90   public MatchPredicate getPredicate() {
91     return predicate;
92   }
93
94   private static boolean validateOneMatch(final PsiElement match, int start, int end, final MatchResultImpl result, final MatchContext matchContext) {
95     final boolean matchresult;
96
97     if (match!=null) {
98       if (start==0 && end==-1 && result.getStart()==0 && result.getEnd()==-1) {
99         matchresult = matchContext.getMatcher().match(match,result.getMatchRef().getElement());
100       } else {
101         matchresult = StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end).equals(
102           result.getMatchImage()
103         );
104       }
105     } else {
106       matchresult = result.isMatchImageNull();
107     }
108
109     return matchresult;
110   }
111
112   public boolean validate(final PsiElement match, int start, int end, MatchContext context) {
113     if (predicate!=null) {
114       if(!predicate.match(null,match,start,end,context)) return false;
115     }
116
117     if (maxOccurs==0) {
118       totalMatchedOccurs++;
119       return false;
120     }
121
122     MatchResultImpl result = context.getResult().findSon(name);
123     
124     if (result == null && context.getPreviousResult() != null) {
125       result = context.getPreviousResult().findSon(name);
126     }
127
128     if (result!=null) {
129       if (minOccurs == 1 && maxOccurs == 1) {
130         // check if they are the same
131         return validateOneMatch(match, start, end, result,context);
132       } else if (maxOccurs > 1 && totalMatchedOccurs!=-1) {
133         final int size = result.getAllSons().size();
134         if (matchedOccurs >= size) {
135           return false;
136         }
137         result = size == 0 ?result:(MatchResultImpl)result.getAllSons().get(matchedOccurs);
138         // check if they are the same
139         return validateOneMatch(match, start, end, result, context);
140       }
141     }
142
143     return true;
144   }
145
146   public boolean match(final PsiElement node, final PsiElement match, MatchContext context) {
147     if (!super.match(node,match,context)) return false;
148
149     return matchHandler == null ?
150            context.getMatcher().match(node, match):
151            matchHandler.match(node,match,context);
152   }
153
154   public boolean handle(final PsiElement match, MatchContext context) {
155     return handle(match,0,-1,context);
156   }
157
158   public void addResult(PsiElement match,int start, int end,MatchContext context) {
159     if (totalMatchedOccurs == -1) {
160       final MatchResultImpl matchResult = context.getResult();
161       final MatchResultImpl substitution = matchResult.findSon(name);
162
163       if (substitution == null) {
164         matchResult.addSon( createMatch(match,start,end) );
165       } else if (maxOccurs > 1) {
166         final MatchResultImpl result = createMatch(match,start,end);
167   
168         if (!substitution.isMultipleMatch()) {
169           // adding intermediate node to contain all multiple matches
170           final MatchResultImpl sonresult = new MatchResultImpl(
171             substitution.getName(),
172             substitution.getMatchImage(),
173             substitution.getMatchRef(),
174             substitution.getStart(),
175             substitution.getEnd(),
176             target
177           );
178
179           substitution.setMatchRef(
180             new SmartPsiPointer(match == null ? null : match)
181           );
182
183           substitution.setMultipleMatch(true);
184
185           if (substitution.isScopeMatch()) {
186             substitution.setScopeMatch(false);
187             sonresult.setScopeMatch(true);
188             for(MatchResult r:substitution.getAllSons()) sonresult.addSon((MatchResultImpl)r);
189             substitution.clearMatches();
190           }
191
192           substitution.addSon( sonresult);
193         } 
194   
195         substitution.addSon( result );
196       }
197     }
198   }
199
200   public boolean handle(final PsiElement match, int start, int end, MatchContext context) {
201     if (!validate(match,start,end,context)) {
202       myNestedResult = null;
203       
204       //if (maxOccurs==1 && minOccurs==1) {
205       //  if (context.hasResult()) context.getResult().removeSon(name);
206       //}
207       // @todo we may fail fast the match by throwing an exception
208
209       return false;
210     }
211
212     if (!Configuration.CONTEXT_VAR_NAME.equals(name)) addResult(match, start, end, context);
213
214     return true;
215   }
216
217   private MatchResultImpl createMatch(final PsiElement match, int start, int end) {
218     final String image = match == null ? null : StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end);
219     final SmartPsiPointer ref = new SmartPsiPointer(match);
220
221     final MatchResultImpl result = myNestedResult == null ? new MatchResultImpl(
222       name,
223       image,
224       ref,
225       start,
226       end,
227       target
228     ) : myNestedResult;
229
230     if (myNestedResult != null) {
231       myNestedResult.setName( name );
232       myNestedResult.setMatchImage( image );
233       myNestedResult.setMatchRef( ref );
234       myNestedResult.setStart( start );
235       myNestedResult.setEnd( end );
236       myNestedResult.setTarget( target );
237       myNestedResult = null;
238     }
239
240     return result;
241   }
242
243   boolean validate(MatchContext context, Class elementContext) {
244     MatchResult substitution = context.getResult().findSon(name);
245
246     if (minOccurs >= 1 &&
247         ( substitution == null ||
248           StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()).getElementContextByPsi(substitution.getMatchRef().getElement()) != elementContext
249         )
250        ) {
251       return false;
252     } else if (maxOccurs <= 1 &&
253         substitution!=null && substitution.hasSons()
254     ) {
255       return false;
256     } else if (maxOccurs==0 && totalMatchedOccurs!=-1) {
257       return false;
258     }
259     return true;
260   }
261
262   public int getMinOccurs() {
263     return minOccurs;
264   }
265
266   public int getMaxOccurs() {
267     return maxOccurs;
268   }
269
270   private void removeLastResults(int numberOfResults, MatchContext context) {
271     if (numberOfResults == 0) return;
272     final MatchResultImpl substitution = context.getResult().findSon(name);
273
274     if (substitution!=null) {
275       final List<PsiElement> matchedNodes = context.getMatchedNodes();
276
277       if (substitution.hasSons()) {
278         final List<MatchResult> sons = substitution.getMatches();
279
280         while(numberOfResults > 0) {
281           --numberOfResults;
282           final MatchResult matchResult = sons.remove(sons.size() - 1);
283           if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch());
284         }
285
286         if (sons.isEmpty()) {
287           context.getResult().removeSon(name);
288         }
289       } else {
290         final MatchResultImpl matchResult = context.getResult().removeSon(name);
291         if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch());
292       }
293     }
294   }
295
296   public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) {
297     final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null;
298     context.setResult(null);
299
300     try {
301
302       if (patternNodes.hasNext() && !matchedNodes.hasNext()) {
303         return validateSatisfactionOfHandlers(patternNodes, context);
304       }
305
306       Set<PsiElement> matchedElements = null;
307
308       for(; patternNodes.hasNext(); patternNodes.advance()) {
309         int matchedOccurs = 0;
310         final PsiElement patternNode = patternNodes.current();
311         final CompiledPattern pattern = context.getPattern();
312         final MatchingHandler handler = pattern.getHandler(patternNode);
313
314         final PsiElement startMatching = matchedNodes.current();
315         do {
316           final PsiElement element = handler.getPinnedNode(null);
317           final PsiElement matchedNode = (element != null) ? element : matchedNodes.current();
318
319           if (element == null) matchedNodes.advance();
320           if (!matchedNodes.hasNext()) matchedNodes.reset();
321
322           if (matchedOccurs <= maxOccurs &&
323               (matchedElements == null || !matchedElements.contains(matchedNode))) {
324
325             if (handler.match(patternNode, matchedNode, context)) {
326               ++matchedOccurs;
327               if (matchedElements == null) matchedElements = new HashSet<PsiElement>();
328               matchedElements.add(matchedNode);
329               if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) {
330                 break;
331               }
332             } else if (element != null) {
333               return false;
334             }
335
336             // clear state of dependent objects
337             clearingVisitor.clearState(pattern, patternNode);
338           }
339
340           // passed of elements and does not found the match
341           if (startMatching == matchedNodes.current()) {
342             final boolean result = validateSatisfactionOfHandlers(patternNodes, context) &&
343                                    matchedOccurs >= minOccurs && matchedOccurs <= maxOccurs;
344             if (result && context.getMatchedElementsListener() != null) {
345               context.getMatchedElementsListener().matchedElements(matchedElements);
346             }
347             return result;
348           }
349         } while(true);
350
351         if (!handler.shouldAdvanceThePatternFor(patternNode, null)) {
352           patternNodes.rewind();
353         }
354       }
355
356       final boolean result = validateSatisfactionOfHandlers(patternNodes, context);
357       if (result && context.getMatchedElementsListener() != null) {
358         context.getMatchedElementsListener().matchedElements(matchedElements);
359       }
360       return result;
361     } finally {
362       if (saveResult!=null) {
363         if (context.hasResult()) {
364           saveResult.getMatches().addAll(context.getResult().getMatches());
365         }
366         context.setResult(saveResult);
367       }
368     }
369   }
370
371   public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
372     return doMatchSequentially(nodes, nodes2, context);
373   }
374
375   protected boolean doMatchSequentiallyBySimpleHandler(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
376     final boolean oldValue = context.shouldRecursivelyMatch();
377     context.setShouldRecursivelyMatch(false);
378     final boolean result = super.matchSequentially(nodes, nodes2, context);
379     context.setShouldRecursivelyMatch(oldValue);
380     return result;
381   }
382
383   protected boolean doMatchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) {
384     final int previousMatchedOccurs = matchedOccurs;
385
386     FilteringNodeIterator fNodes2 = new FilteringNodeIterator(nodes2, VARS_DELIM_FILTER);
387
388     try {
389       MatchingHandler handler = context.getPattern().getHandler(nodes.current());
390       matchedOccurs = 0;
391
392       boolean flag = false;
393
394       while(fNodes2.hasNext() && matchedOccurs < minOccurs) {
395         if (handler.match(nodes.current(), nodes2.current(), context)) {
396           ++matchedOccurs;
397         } else {
398           break;
399         }
400         fNodes2.advance();
401         flag = true;
402       }
403
404       if (matchedOccurs!=minOccurs) {
405         // failed even for min occurs
406         removeLastResults(matchedOccurs, context);
407         fNodes2.rewind(matchedOccurs);
408         return false;
409       }
410
411       if (greedy)  {
412         // go greedily to maxOccurs
413
414         while(fNodes2.hasNext() && matchedOccurs < maxOccurs) {
415           if (handler.match(nodes.current(), nodes2.current(), context)) {
416             ++matchedOccurs;
417           } else {
418             // no more matches could take!
419             break;
420           }
421           fNodes2.advance();
422           flag = true;
423         }
424
425         if (flag) {
426           fNodes2.rewind();
427           nodes2.advance();
428         }
429
430         nodes.advance();
431
432         if (nodes.hasNext()) {
433           final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
434
435           while(matchedOccurs >= minOccurs) {
436             if (nextHandler.matchSequentially(nodes, nodes2, context)) {
437               totalMatchedOccurs = matchedOccurs;
438               // match found
439               return true;
440             }
441
442             if (matchedOccurs > 0) {
443               nodes2.rewind();
444               removeLastResults(1,context);
445             }
446             --matchedOccurs;
447           }
448
449           if (matchedOccurs > 0) {
450             removeLastResults(matchedOccurs, context);
451           }
452           nodes.rewind();
453           return false;
454         } else {
455           // match found
456           if (handler.isMatchSequentiallySucceeded(nodes2)) {
457             return checkSameOccurrencesConstraint();
458           }
459           removeLastResults(matchedOccurs, context);
460           return false;
461         }
462       } else {
463         nodes.advance();
464
465         if (flag) {
466           fNodes2.rewind();
467           nodes2.advance();
468         }
469
470         if (nodes.hasNext()) {
471           final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current());
472
473           flag = false;
474
475           while(nodes2.hasNext() && matchedOccurs <= maxOccurs) {
476             if (nextHandler.matchSequentially(nodes, nodes2, context)) {
477               return checkSameOccurrencesConstraint();
478             }
479
480             if (flag) {
481               nodes2.rewind();
482               fNodes2.advance();
483             }
484
485             if (handler.match(nodes.current(), nodes2.current(), context)) {
486               matchedOccurs++;
487             } else {
488               nodes.rewind();
489               removeLastResults(matchedOccurs,context);
490               return false;
491             }
492             nodes2.advance();
493             flag = true;
494           }
495
496           nodes.rewind();
497           removeLastResults(matchedOccurs,context);
498           return false;
499         } else {
500           return checkSameOccurrencesConstraint();
501         }
502       }
503     } finally {
504       matchedOccurs = previousMatchedOccurs;
505     }
506   }
507
508   private boolean checkSameOccurrencesConstraint() {
509     if (totalMatchedOccurs == -1) {
510       totalMatchedOccurs = matchedOccurs;
511       return true;
512     }
513     else {
514       return totalMatchedOccurs == matchedOccurs;
515     }
516   }
517
518   public void setTarget(boolean target) {
519     this.target = target;
520   }
521
522   public void setMatchHandler(MatchingHandler matchHandler) {
523     this.matchHandler = matchHandler;
524   }
525
526   public boolean isTarget() {
527     return target;
528   }
529
530   public String getName() {
531     return name;
532   }
533
534   public void reset() {
535     super.reset();
536     totalMatchedOccurs = -1;
537   }
538
539   public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) {
540     if(maxOccurs > 1) return false;
541     return super.shouldAdvanceThePatternFor(patternElement,matchedElement);
542   }
543
544   public void setNestedResult(final MatchResultImpl nestedResult) {
545     myNestedResult = nestedResult;
546   }
547
548   public MatchResultImpl getNestedResult() {
549     return myNestedResult;
550   }
551 }