SSR: find parameterized method calls (IDEA-154740)
[idea/community.git] / java / structuralsearch-java / src / com / intellij / structuralsearch / impl / matcher / JavaMatchingVisitor.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.structuralsearch.impl.matcher;
17
18 import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator;
19 import com.intellij.dupLocator.iterators.NodeIterator;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.psi.*;
22 import com.intellij.psi.javadoc.PsiDocComment;
23 import com.intellij.psi.javadoc.PsiDocTag;
24 import com.intellij.psi.util.PsiTreeUtil;
25 import com.intellij.psi.util.PsiUtil;
26 import com.intellij.structuralsearch.MatchOptions;
27 import com.intellij.structuralsearch.MatchResult;
28 import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter;
29 import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate;
30 import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler;
31 import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler;
32 import com.intellij.structuralsearch.impl.matcher.iterators.DocValuesIterator;
33 import com.intellij.structuralsearch.impl.matcher.iterators.HierarchyNodeIterator;
34 import com.intellij.structuralsearch.impl.matcher.predicates.NotPredicate;
35 import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate;
36 import com.intellij.util.containers.ContainerUtil;
37 import org.jetbrains.annotations.Contract;
38 import org.jetbrains.annotations.NotNull;
39
40 import java.util.*;
41
42 /**
43  * @author Eugene.Kudelevsky
44  */
45 public class JavaMatchingVisitor extends JavaElementVisitor {
46   public static final String[] MODIFIERS = {
47     PsiModifier.PUBLIC, PsiModifier.PROTECTED, PsiModifier.PRIVATE, PsiModifier.STATIC, PsiModifier.ABSTRACT, PsiModifier.FINAL,
48     PsiModifier.NATIVE, PsiModifier.SYNCHRONIZED, PsiModifier.STRICTFP, PsiModifier.TRANSIENT, PsiModifier.VOLATILE, PsiModifier.DEFAULT
49   };
50   private final GlobalMatchingVisitor myMatchingVisitor;
51   private PsiClass myClazz;
52
53   static {
54     Arrays.sort(MODIFIERS);
55   }
56
57   public JavaMatchingVisitor(GlobalMatchingVisitor matchingVisitor) {
58     this.myMatchingVisitor = matchingVisitor;
59   }
60
61   @Override
62   public void visitComment(PsiComment comment) {
63     PsiComment comment2 = null;
64
65     if (!(myMatchingVisitor.getElement() instanceof PsiComment)) {
66       if (myMatchingVisitor.getElement() instanceof PsiMember) {
67         final PsiElement[] children = myMatchingVisitor.getElement().getChildren();
68         if (children[0] instanceof PsiComment) {
69           comment2 = (PsiComment)children[0];
70         }
71       }
72     }
73     else {
74       comment2 = (PsiComment)myMatchingVisitor.getElement();
75     }
76
77     if (comment2 == null) {
78       myMatchingVisitor.setResult(false);
79       return;
80     }
81
82     final Object userData = comment.getUserData(CompiledPattern.HANDLER_KEY);
83
84     if (userData instanceof String) {
85       final String str = (String)userData;
86       int end = comment2.getTextLength();
87
88       if (comment2.getTokenType() == JavaTokenType.C_STYLE_COMMENT) {
89         end -= 2;
90       }
91       myMatchingVisitor.setResult(((SubstitutionHandler)myMatchingVisitor.getMatchContext().getPattern().getHandler(str)).handle(
92         comment2,
93         2,
94         end,
95         myMatchingVisitor.getMatchContext()
96       ));
97     }
98     else if (userData instanceof MatchingHandler) {
99       myMatchingVisitor.setResult(((MatchingHandler)userData).match(comment, comment2, myMatchingVisitor.getMatchContext()));
100     }
101     else {
102       myMatchingVisitor.setResult(myMatchingVisitor.matchText(comment, comment2));
103     }
104   }
105
106   @Override
107   public final void visitModifierList(final PsiModifierList list) {
108     final PsiModifierList list2 = (PsiModifierList)myMatchingVisitor.getElement();
109
110     for (@PsiModifier.ModifierConstant String modifier : MODIFIERS) {
111       if (list.hasModifierProperty(modifier) && !list2.hasModifierProperty(modifier)) {
112         myMatchingVisitor.setResult(false);
113         return;
114       }
115     }
116
117     final PsiAnnotation[] annotations = list.getAnnotations();
118     if (annotations.length > 0) {
119       HashSet<PsiAnnotation> set = new HashSet<PsiAnnotation>(Arrays.asList(annotations));
120
121       for (PsiAnnotation annotation : annotations) {
122         final PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
123
124         if (nameReferenceElement != null && MatchOptions.MODIFIER_ANNOTATION_NAME.equals(nameReferenceElement.getText())) {
125           final PsiAnnotationParameterList parameterList = annotation.getParameterList();
126           final PsiNameValuePair[] attributes = parameterList.getAttributes();
127
128           for (PsiNameValuePair pair : attributes) {
129             final PsiAnnotationMemberValue value = pair.getValue();
130             if (value == null) continue;
131
132             if (value instanceof PsiArrayInitializerMemberValue) {
133               boolean matchedOne = false;
134
135               for (PsiAnnotationMemberValue v : ((PsiArrayInitializerMemberValue)value).getInitializers()) {
136                 if (annotationValueMatchesModifierList(list2, v)) {
137                   matchedOne = true;
138                   break;
139                 }
140               }
141
142               if (!matchedOne) {
143                 myMatchingVisitor.setResult(false);
144                 return;
145               }
146             }
147             else {
148               if (!annotationValueMatchesModifierList(list2, value)) {
149                 myMatchingVisitor.setResult(false);
150                 return;
151               }
152             }
153           }
154
155           set.remove(annotation);
156         }
157       }
158
159       myMatchingVisitor.setResult(set.isEmpty() ||
160                                   myMatchingVisitor.matchInAnyOrder(set.toArray(new PsiAnnotation[set.size()]), list2.getAnnotations()));
161     }
162     else {
163       myMatchingVisitor.setResult(true);
164     }
165   }
166
167   private static boolean annotationValueMatchesModifierList(PsiModifierList list2, PsiAnnotationMemberValue value) {
168     @PsiModifier.ModifierConstant final String name = StringUtil.unquoteString(value.getText());
169     if (MatchOptions.INSTANCE_MODIFIER_NAME.equals(name)) {
170       return !list2.hasModifierProperty(PsiModifier.STATIC) && !list2.hasModifierProperty(PsiModifier.ABSTRACT) &&
171              list2.getParent() instanceof PsiMember;
172     }
173     return list2.hasModifierProperty(name) && (!PsiModifier.PACKAGE_LOCAL.equals(name) || list2.getParent() instanceof PsiMember);
174   }
175
176   @Override
177   public void visitDocTag(final PsiDocTag tag) {
178     final PsiDocTag tag2 = (PsiDocTag)myMatchingVisitor.getElement();
179     final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(tag.getNameElement());
180
181     myMatchingVisitor.setResult(isTypedVar || tag.getName().equals(tag2.getName()));
182
183     PsiElement psiDocTagValue = tag.getValueElement();
184     boolean isTypedValue = false;
185
186     if (myMatchingVisitor.getResult() && psiDocTagValue != null) {
187       final PsiElement[] children = psiDocTagValue.getChildren();
188       if (children.length == 1) {
189         psiDocTagValue = children[0];
190       }
191       isTypedValue = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(psiDocTagValue);
192
193       if (isTypedValue) {
194         if (tag2.getValueElement() != null) {
195           myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(psiDocTagValue, tag2.getValueElement()));
196         }
197         else {
198           myMatchingVisitor.setResult(myMatchingVisitor.allowsAbsenceOfMatch(psiDocTagValue));
199         }
200       }
201     }
202
203     if (myMatchingVisitor.getResult() && !isTypedValue) {
204       myMatchingVisitor.setResult(myMatchingVisitor.matchInAnyOrder(
205         new DocValuesIterator(tag.getFirstChild()),
206         new DocValuesIterator(tag2.getFirstChild())
207       ));
208     }
209
210     if (myMatchingVisitor.getResult() && isTypedVar) {
211       myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(tag.getNameElement(), tag2.getNameElement()));
212     }
213   }
214
215   @Override
216   public void visitDocComment(final PsiDocComment comment) {
217     PsiDocComment comment2;
218
219     if (myMatchingVisitor.getElement() instanceof PsiDocCommentOwner) {
220       comment2 = ((PsiDocCommentOwner)myMatchingVisitor.getElement()).getDocComment();
221
222       if (comment2 == null) {
223         // doc comment are not collapsed for inner classes!
224         myMatchingVisitor.setResult(false);
225         return;
226       }
227     }
228     else {
229       comment2 = (PsiDocComment)myMatchingVisitor.getElement();
230
231       if (myMatchingVisitor.getElement().getParent() instanceof PsiDocCommentOwner) {
232         myMatchingVisitor.setResult(false);
233         return; // we should matched the doc before
234       }
235     }
236
237     if (comment.getTags().length > 0) {
238       myMatchingVisitor.setResult(myMatchingVisitor.matchInAnyOrder(comment.getTags(), comment2.getTags()));
239     }
240     else {
241       visitComment(comment);
242     }
243   }
244
245   @Override
246   public void visitElement(PsiElement el) {
247     myMatchingVisitor.setResult(myMatchingVisitor.matchText(el, myMatchingVisitor.getElement()));
248   }
249
250   @Override
251   public void visitArrayInitializerExpression(PsiArrayInitializerExpression expression) {
252     final PsiArrayInitializerExpression expr2 = (PsiArrayInitializerExpression)myMatchingVisitor.getElement();
253
254     myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(
255       new ArrayBackedNodeIterator(expression.getInitializers()),
256       new ArrayBackedNodeIterator(expr2.getInitializers())
257     ));
258   }
259
260   @Override
261   public void visitClassInitializer(PsiClassInitializer initializer) {
262     PsiClassInitializer initializer2 = (PsiClassInitializer)myMatchingVisitor.getElement();
263     myMatchingVisitor.setResult(myMatchingVisitor.match(initializer.getModifierList(), initializer2.getModifierList()) &&
264                                 myMatchingVisitor.match(initializer.getBody(), initializer2.getBody()));
265   }
266
267   @Override
268   public void visitCodeBlock(PsiCodeBlock block) {
269     myMatchingVisitor.setResult(myMatchingVisitor.matchSons(block, myMatchingVisitor.getElement()));
270   }
271
272   @Override
273   public void visitJavaToken(final PsiJavaToken token) {
274     PsiElement element = myMatchingVisitor.getElement();
275     boolean result;
276
277     if (!(element instanceof PsiJavaToken)) {
278       result = myMatchingVisitor.matchText(token, element);
279     } else {
280       final PsiJavaToken anotherToken = (PsiJavaToken)element;
281
282       result = token.getTokenType() == anotherToken.getTokenType() && myMatchingVisitor.matchText(token, anotherToken);
283     }
284
285     myMatchingVisitor.setResult(result);
286   }
287
288   @Override
289   public void visitAnnotation(PsiAnnotation annotation) {
290     final PsiAnnotation psiAnnotation = (PsiAnnotation)myMatchingVisitor.getElement();
291
292     myMatchingVisitor.setResult(myMatchingVisitor.match(annotation.getNameReferenceElement(), psiAnnotation.getNameReferenceElement()) &&
293                                 myMatchingVisitor
294                                   .matchInAnyOrder(annotation.getParameterList().getAttributes(),
295                                                    psiAnnotation.getParameterList().getAttributes()));
296   }
297
298   @Override
299   public void visitNameValuePair(PsiNameValuePair pair) {
300     final PsiNameValuePair elementNameValuePair = (PsiNameValuePair)myMatchingVisitor.getElement();
301
302     final PsiAnnotationMemberValue annotationInitializer = pair.getValue();
303     myMatchingVisitor.setResult(myMatchingVisitor.match(annotationInitializer, elementNameValuePair.getValue()));
304     if (myMatchingVisitor.getResult()) {
305       final PsiIdentifier nameIdentifier = pair.getNameIdentifier();
306       final PsiIdentifier otherIdentifier = elementNameValuePair.getNameIdentifier();
307       if (nameIdentifier == null) {
308         myMatchingVisitor.setResult(otherIdentifier == null ||
309                                     otherIdentifier.getText().equals(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME));
310       }
311       else {
312         final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(nameIdentifier);
313         if (handler instanceof SubstitutionHandler) {
314           myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(otherIdentifier, myMatchingVisitor.getMatchContext()));
315         }
316         else {
317           myMatchingVisitor.setResult(myMatchingVisitor.match(nameIdentifier, otherIdentifier));
318         }
319       }
320     }
321   }
322
323   private boolean checkHierarchy(PsiMember element, PsiMember patternElement) {
324     final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(patternElement);
325     if (handler instanceof SubstitutionHandler) {
326       final SubstitutionHandler handler2 = (SubstitutionHandler)handler;
327
328       if (!handler2.isSubtype()) {
329         if (handler2.isStrictSubtype()) {
330           // check if element is declared not in current class  (in ancestors)
331           return element.getContainingClass() != myClazz;
332         }
333       }
334       else {
335         return true;
336       }
337     }
338
339     // check if element is declared in current class (not in ancestors)
340     return myClazz == null || element.getContainingClass() == myClazz;
341   }
342
343   @Override
344   public void visitField(PsiField psiField) {
345     final PsiDocComment comment = psiField.getDocComment();
346     final PsiField other = (PsiField)myMatchingVisitor.getElement();
347     if (comment != null) {
348       myMatchingVisitor.setResult(myMatchingVisitor.match(comment, other));
349       if (!myMatchingVisitor.getResult()) return;
350     }
351     if (!checkHierarchy(other, psiField)) {
352       myMatchingVisitor.setResult(false);
353       return;
354     }
355     super.visitField(psiField);
356   }
357
358   @Override
359   public void visitAnonymousClass(final PsiAnonymousClass clazz) {
360     final PsiAnonymousClass clazz2 = (PsiAnonymousClass)myMatchingVisitor.getElement();
361     final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(clazz.getFirstChild());
362
363     myMatchingVisitor.setResult((myMatchingVisitor.match(clazz.getBaseClassReference(), clazz2.getBaseClassReference()) || isTypedVar) &&
364                                 myMatchingVisitor.matchSons(clazz.getArgumentList(), clazz2.getArgumentList()) &&
365                                 compareClasses(clazz, clazz2));
366
367     if (myMatchingVisitor.getResult() && isTypedVar) {
368       myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(clazz.getFirstChild(), clazz2.getFirstChild()));
369     }
370   }
371
372   @Override
373   public void visitLambdaExpression(PsiLambdaExpression expression) {
374     final PsiElement other = myMatchingVisitor.getElement();
375     if (other instanceof PsiLambdaExpression) {
376       final PsiLambdaExpression expression2 = (PsiLambdaExpression)other;
377       boolean result = true;
378       final PsiParameterList parameterList1 = expression.getParameterList();
379       if (parameterList1.getParametersCount() != 0) {
380         result = myMatchingVisitor.matchSons(parameterList1, expression2.getParameterList());
381       }
382       final PsiElement body1 = getElementToMatch(expression.getBody());
383       if (body1 != null && result) {
384         result = myMatchingVisitor.matchSequentially(body1, getElementToMatch(expression2.getBody()));
385       }
386       myMatchingVisitor.setResult(result);
387     }
388     else {
389       myMatchingVisitor.setResult(false);
390     }
391   }
392
393   private static PsiElement getElementToMatch(PsiElement element) {
394     if (element instanceof PsiCodeBlock) {
395       element = PsiTreeUtil.getChildOfAnyType(element, PsiStatement.class, PsiComment.class);
396     }
397     if (element instanceof PsiExpressionStatement) {
398       element = ((PsiExpressionStatement)element).getExpression();
399     }
400     if (element instanceof PsiReturnStatement) {
401       element = ((PsiReturnStatement)element).getReturnValue();
402     }
403     return element;
404   }
405
406   private boolean matchInAnyOrder(final PsiReferenceList elements, final PsiReferenceList elements2) {
407     if ((elements == null && myMatchingVisitor.isLeftLooseMatching()) ||
408         elements == elements2 // null
409       ) {
410       return true;
411     }
412
413     return myMatchingVisitor.matchInAnyOrder(
414       elements.getReferenceElements(),
415       (elements2 != null) ? elements2.getReferenceElements() : PsiElement.EMPTY_ARRAY
416     );
417   }
418
419   private boolean compareClasses(final PsiClass clazz, final PsiClass clazz2) {
420     final PsiClass saveClazz = this.myClazz;
421     final MatchContext.MatchedElementsListener oldListener = myMatchingVisitor.getMatchContext().getMatchedElementsListener();
422
423     this.myClazz = clazz2;
424
425     final CompiledPattern pattern = myMatchingVisitor.getMatchContext().getPattern();
426     assert pattern instanceof JavaCompiledPattern;
427     final JavaCompiledPattern javaPattern = (JavaCompiledPattern)pattern;
428
429     final String unmatchedHandlerName = clazz.getUserData(JavaCompiledPattern.ALL_CLASS_CONTENT_VAR_NAME_KEY);
430     final MatchingHandler allRemainingClassContentElementHandler = unmatchedHandlerName != null ? pattern.getHandler(unmatchedHandlerName) : null;
431     MatchContext.MatchedElementsListener newListener = null;
432
433     if (allRemainingClassContentElementHandler != null) {
434       myMatchingVisitor.getMatchContext().setMatchedElementsListener(
435         newListener = new MatchContext.MatchedElementsListener() {
436           private Set<PsiElement> myMatchedElements;
437
438           public void matchedElements(Collection<PsiElement> matchedElements) {
439             if (matchedElements == null) return;
440             if (myMatchedElements == null) {
441               myMatchedElements = new HashSet<PsiElement>(matchedElements);
442             }
443             else {
444               myMatchedElements.addAll(matchedElements);
445             }
446           }
447
448           public void commitUnmatched() {
449             final SubstitutionHandler handler = (SubstitutionHandler)allRemainingClassContentElementHandler;
450
451             for (PsiElement el = clazz2.getFirstChild(); el != null; el = el.getNextSibling()) {
452               if (el instanceof PsiMember && (myMatchedElements == null || !myMatchedElements.contains(el))) {
453                 handler.handle(el, myMatchingVisitor.getMatchContext());
454               }
455             }
456           }
457         }
458       );
459     }
460
461     boolean result = false;
462     try {
463       final boolean templateIsInterface = clazz.isInterface();
464       if (templateIsInterface != clazz2.isInterface()) return false;
465       if (templateIsInterface && clazz.isAnnotationType() && !clazz2.isAnnotationType()) return false;
466       if (clazz.isEnum() && !clazz2.isEnum()) return false;
467
468       if (!matchInAnyOrder(clazz.getExtendsList(), clazz2.getExtendsList())) {
469         return false;
470       }
471
472       // check if implements is in extended classes implements
473       final PsiReferenceList implementsList = clazz.getImplementsList();
474       if (implementsList != null) {
475         if (!matchInAnyOrder(implementsList, clazz2.getImplementsList())) {
476           final PsiReferenceList anotherExtendsList = clazz2.getExtendsList();
477           final PsiJavaCodeReferenceElement[] referenceElements = implementsList.getReferenceElements();
478
479           boolean accepted = false;
480
481           if (referenceElements.length > 0 && anotherExtendsList != null) {
482             final HierarchyNodeIterator iterator = new HierarchyNodeIterator(clazz2, true, true, false);
483
484             accepted = myMatchingVisitor.matchInAnyOrder(new ArrayBackedNodeIterator(referenceElements), iterator);
485           }
486
487           if (!accepted) return false;
488         }
489       }
490
491       final PsiField[] fields = clazz.getFields();
492
493       if (fields.length > 0) {
494         final PsiField[] fields2 = javaPattern.isRequestsSuperFields() ?
495                                    clazz2.getAllFields() :
496                                    clazz2.getFields();
497
498         if (!myMatchingVisitor.matchInAnyOrder(fields, fields2)) {
499           return false;
500         }
501       }
502
503       final PsiMethod[] methods = clazz.getMethods();
504
505       if (methods.length > 0) {
506         final PsiMethod[] methods2 = javaPattern.isRequestsSuperMethods() ?
507                                      clazz2.getAllMethods() :
508                                      clazz2.getMethods();
509
510         if (!myMatchingVisitor.matchInAnyOrder(methods, methods2)) {
511           return false;
512         }
513       }
514
515       final PsiClass[] nestedClasses = clazz.getInnerClasses();
516
517       if (nestedClasses.length > 0) {
518         final PsiClass[] nestedClasses2 = javaPattern.isRequestsSuperInners() ?
519                                           clazz2.getAllInnerClasses() :
520                                           clazz2.getInnerClasses();
521
522         if (!myMatchingVisitor.matchInAnyOrder(nestedClasses, nestedClasses2)) {
523           return false;
524         }
525       }
526
527       final PsiClassInitializer[] initializers = clazz.getInitializers();
528       if (initializers.length > 0) {
529         final PsiClassInitializer[] initializers2 = clazz2.getInitializers();
530
531         if (!myMatchingVisitor.matchInAnyOrder(initializers, initializers2)) {
532           return false;
533         }
534       }
535
536       result = true;
537       return result;
538     }
539     finally {
540       if (result && newListener != null) newListener.commitUnmatched();
541       this.myClazz = saveClazz;
542       myMatchingVisitor.getMatchContext().setMatchedElementsListener(oldListener);
543     }
544   }
545
546   private boolean compareBody(final PsiElement el1, final PsiElement el2) {
547     PsiElement compareElement1 = el1;
548     PsiElement compareElement2 = el2;
549
550     if (myMatchingVisitor.getMatchContext().getOptions().isLooseMatching()) {
551       if (el1 instanceof PsiBlockStatement) {
552         compareElement1 = ((PsiBlockStatement)el1).getCodeBlock().getFirstChild();
553       }
554
555       if (el2 instanceof PsiBlockStatement) {
556         compareElement2 = ((PsiBlockStatement)el2).getCodeBlock().getFirstChild();
557       }
558     }
559
560     return myMatchingVisitor.matchSequentially(compareElement1, compareElement2);
561   }
562
563   @Override
564   public void visitArrayAccessExpression(final PsiArrayAccessExpression slice) {
565     final PsiElement other = myMatchingVisitor.getElement();
566     if (other instanceof PsiArrayAccessExpression) {
567       final PsiArrayAccessExpression slice2 = (PsiArrayAccessExpression)other;
568       myMatchingVisitor.setResult(myMatchingVisitor.match(slice.getArrayExpression(), slice2.getArrayExpression()) &&
569                                   myMatchingVisitor.match(slice.getIndexExpression(), slice2.getIndexExpression()));
570     }
571     else {
572       myMatchingVisitor.setResult(false);
573     }
574   }
575
576   @Override
577   public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) {
578     final PsiElement element = myMatchingVisitor.getElement();
579     if (!(element instanceof PsiMethodReferenceExpression)) {
580       myMatchingVisitor.setResult(false);
581       return;
582     }
583     super.visitMethodReferenceExpression(expression);
584   }
585
586   @Override
587   public void visitReferenceExpression(final PsiReferenceExpression reference) {
588     final PsiExpression qualifier = reference.getQualifierExpression();
589
590     final PsiElement nameElement = reference.getReferenceNameElement();
591     final MatchContext context = myMatchingVisitor.getMatchContext();
592     MatchingHandler _handler = nameElement != null ? context.getPattern().getHandlerSimple(nameElement) : null;
593     if (!(_handler instanceof SubstitutionHandler)) _handler = context.getPattern().getHandlerSimple(reference);
594
595     final PsiElement element = myMatchingVisitor.getElement();
596     PsiElement other = element instanceof PsiExpression && context.getOptions().isLooseMatching() ?
597                        PsiUtil.skipParenthesizedExprDown((PsiExpression)element) :
598                        element;
599     if (_handler instanceof SubstitutionHandler &&
600         !(context.getPattern().getHandlerSimple(qualifier) instanceof SubstitutionHandler) &&
601         !(qualifier instanceof PsiThisExpression)
602       ) {
603       if (other instanceof PsiReferenceExpression) {
604         final PsiReferenceExpression psiReferenceExpression = (PsiReferenceExpression)other;
605
606         final PsiExpression qualifier2 = psiReferenceExpression.getQualifierExpression();
607         if (qualifier2 == null || (context.getOptions().isLooseMatching() && qualifier2 instanceof PsiThisExpression)) {
608           other = psiReferenceExpression.getReferenceNameElement();
609         }
610       }
611
612       final SubstitutionHandler handler = (SubstitutionHandler)_handler;
613       if (handler.isSubtype() || handler.isStrictSubtype()) {
614         myMatchingVisitor.setResult(checkMatchWithingHierarchy(other, handler, reference));
615       }
616       else {
617         myMatchingVisitor.setResult(handler.handle(other, context));
618       }
619
620       return;
621     }
622
623     if (!(other instanceof PsiReferenceExpression)) {
624       myMatchingVisitor.setResult(false);
625       return;
626     }
627
628     final PsiReferenceExpression reference2 = (PsiReferenceExpression)other;
629
630     // just variable
631     final PsiExpression reference2Qualifier = reference2.getQualifierExpression();
632     if (qualifier == null && reference2Qualifier == null) {
633       myMatchingVisitor.setResult(myMatchingVisitor.matchText(reference.getReferenceNameElement(), reference2.getReferenceNameElement()));
634       return;
635     }
636
637     // handle field selection
638     if (!(other.getParent() instanceof PsiMethodCallExpression) && qualifier != null) {
639       final PsiElement referenceElement = reference.getReferenceNameElement();
640       final PsiElement referenceElement2 = reference2.getReferenceNameElement();
641
642       if (context.getPattern().isTypedVar(referenceElement)) {
643         myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(referenceElement, referenceElement2));
644       }
645       else {
646         myMatchingVisitor.setResult(myMatchingVisitor.matchText(referenceElement, referenceElement2));
647       }
648
649       if (!myMatchingVisitor.getResult()) {
650         return;
651       }
652       if (reference2Qualifier != null) {
653         myMatchingVisitor.setResult(myMatchingVisitor.match(qualifier, reference2Qualifier));
654       }
655       else {
656         final PsiElement referencedElement = MatchUtils.getReferencedElement(other);
657         if (referencedElement instanceof PsiField) {
658           final PsiField field = (PsiField)referencedElement;
659           if (qualifier instanceof PsiThisExpression) {
660             myMatchingVisitor.setResult(!field.hasModifierProperty(PsiModifier.STATIC));
661             return;
662           }
663         }
664         final MatchingHandler handler = context.getPattern().getHandler(qualifier);
665         matchImplicitQualifier(handler, referencedElement, context);
666       }
667
668       return;
669     }
670
671     myMatchingVisitor.setResult(false);
672   }
673
674   private static int getArrayDimensions(final PsiElement element) {
675     if (element == null) {
676       return 0;
677     }
678     final PsiElement parent = element.getParent();
679     if (parent instanceof PsiVariable) {
680       final PsiVariable variable = (PsiVariable)parent;
681       final PsiType type = variable.getType();
682       return type.getArrayDimensions();
683     }
684     else if (parent instanceof PsiMethod) {
685       final PsiMethod method = (PsiMethod)parent;
686       final PsiType type = method.getReturnType();
687       return (type == null) ? 0 : type.getArrayDimensions();
688     }
689     else if (element instanceof PsiTypeElement) {
690       final PsiTypeElement typeElement = (PsiTypeElement)element;
691       final PsiType type = typeElement.getType();
692       return type.getArrayDimensions();
693     }
694     return 0;
695   }
696
697   private static PsiTypeElement getInnermostComponentTypeElement(PsiTypeElement typeElement) {
698     PsiElement child = typeElement.getFirstChild();
699     while (child instanceof PsiTypeElement) {
700       typeElement = (PsiTypeElement)child;
701       child = typeElement.getFirstChild();
702     }
703     return typeElement;
704   }
705
706   private static PsiElement getInnermostComponent(PsiElement element) {
707     if (!(element instanceof PsiTypeElement)) {
708       return element;
709     }
710     final PsiTypeElement typeElement = (PsiTypeElement)element;
711     final PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement();
712     return (referenceElement != null) ? referenceElement : getInnermostComponentTypeElement(typeElement);
713   }
714
715   private void copyResults(final MatchResultImpl ourResult) {
716     if (ourResult.hasSons()) {
717       for (MatchResult son : ourResult.getAllSons()) {
718         myMatchingVisitor.getMatchContext().getResult().addSon((MatchResultImpl)son);
719       }
720     }
721   }
722
723   private boolean matchType(final PsiElement patternType, final PsiElement matchedType) {
724     PsiElement patternElement = getInnermostComponent(patternType);
725     PsiElement matchedElement = getInnermostComponent(matchedType);
726
727     PsiElement[] typeParameters = null;
728     if (matchedElement instanceof PsiJavaCodeReferenceElement) {
729       final PsiReferenceParameterList parameterList = ((PsiJavaCodeReferenceElement)matchedElement).getParameterList();
730       if (parameterList != null) {
731         typeParameters = parameterList.getTypeParameterElements();
732       }
733     }
734     else if (matchedElement instanceof PsiTypeParameter) {
735       matchedElement = ((PsiTypeParameter)matchedElement).getNameIdentifier();
736     }
737     else if (matchedElement instanceof PsiClass && ((PsiClass)matchedElement).hasTypeParameters()) {
738       typeParameters = ((PsiClass)matchedElement).getTypeParameters();
739       matchedElement = ((PsiClass)matchedElement).getNameIdentifier();
740     }
741     else if (matchedElement instanceof PsiMethod && ((PsiMethod)matchedElement).hasTypeParameters()) {
742       typeParameters = ((PsiMethod)matchedType).getTypeParameters();
743       matchedElement = ((PsiMethod)matchedType).getNameIdentifier();
744     }
745
746     if (patternElement instanceof PsiJavaCodeReferenceElement) {
747       final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)patternElement;
748       final PsiReferenceParameterList list = referenceElement.getParameterList();
749       if (list != null) {
750         final PsiTypeElement[] elements = list.getTypeParameterElements();
751         if (elements.length > 0 && (typeParameters == null || !myMatchingVisitor.matchSequentially(elements, typeParameters))) {
752           return false;
753         }
754       }
755       patternElement = referenceElement.getReferenceNameElement();
756     }
757
758     final int matchedArrayDimensions = getArrayDimensions(matchedType);
759     final int patternArrayDimensions = getArrayDimensions(patternType);
760
761     if (myMatchingVisitor.getMatchContext().getPattern().isTypedVar(patternElement)) {
762       final SubstitutionHandler handler = (SubstitutionHandler)myMatchingVisitor.getMatchContext().getPattern().getHandler(patternElement);
763
764       RegExpPredicate regExpPredicate = null;
765
766       if (patternArrayDimensions != 0) {
767         if (patternArrayDimensions != matchedArrayDimensions) {
768           return false;
769         }
770       }
771       else if (matchedArrayDimensions != 0) {
772         regExpPredicate = MatchingHandler.getSimpleRegExpPredicate(handler);
773
774         if (regExpPredicate != null) {
775           regExpPredicate.setNodeTextGenerator(new RegExpPredicate.NodeTextGenerator() {
776             public String getText(PsiElement element) {
777               StringBuilder builder = new StringBuilder(RegExpPredicate.getMeaningfulText(element));
778               for (int i = 0; i < matchedArrayDimensions; ++i) builder.append("[]");
779               return builder.toString();
780             }
781           });
782         }
783       }
784
785       try {
786         if (handler.isSubtype() || handler.isStrictSubtype()) {
787           return checkMatchWithingHierarchy(matchedElement, handler, patternElement);
788         }
789         else {
790           return handler.handle(matchedElement, myMatchingVisitor.getMatchContext());
791         }
792       }
793       finally {
794         if (regExpPredicate != null) regExpPredicate.setNodeTextGenerator(null);
795       }
796     }
797
798     if (matchedArrayDimensions != patternArrayDimensions) {
799       return false;
800     }
801
802     if (patternElement instanceof PsiIdentifier) {
803       final PsiElement parent = patternElement.getParent();
804       if (parent instanceof PsiJavaCodeReferenceElement) {
805         patternElement = parent;
806       }
807     }
808     if (matchedElement instanceof PsiIdentifier) {
809       final PsiElement parent = matchedElement.getParent();
810       if (parent instanceof PsiJavaCodeReferenceElement) {
811         matchedElement = parent;
812       }
813     }
814     final String text = getText(patternElement);
815     final String text2 = getText(matchedElement);
816     final boolean caseSensitive = myMatchingVisitor.getMatchContext().getOptions().isCaseSensitiveMatch();
817     final boolean equalsIgnorePackage = MatchUtils.compareWithNoDifferenceToPackage(text, text2, !caseSensitive);
818     if (equalsIgnorePackage || !(matchedElement instanceof PsiJavaReference)) {
819       return equalsIgnorePackage;
820     }
821     else {
822       final PsiElement element2 = ((PsiJavaReference)matchedElement).resolve();
823
824       if (element2 instanceof PsiClass) {
825         final String name = ((PsiClass)element2).getQualifiedName();
826         return caseSensitive ? text.equals(name) : text.equalsIgnoreCase(name);
827       }
828       else {
829         return MatchUtils.compareWithNoDifferenceToPackage(text, text2, !caseSensitive);
830       }
831     }
832   }
833
834   @Contract(pure = true)
835   private static String getText(@NotNull PsiElement element) {
836     String result;
837     if (element instanceof PsiClass) {
838       result = ((PsiClass)element).getQualifiedName();
839       if (result == null) result = element.getText();
840     } else {
841       result = element.getText();
842     }
843     final int whitespace = lastIndexOfWhitespace(result);
844     if (whitespace >= 0) {
845       // strips off any annotations
846       result = result.substring(whitespace + 1);
847     }
848     final int index = result.indexOf('<');
849     if (index == -1) {
850       return result;
851     }
852     return result.substring(0, index);
853   }
854
855   @Contract(pure = true)
856   private static int lastIndexOfWhitespace(@NotNull CharSequence s) {
857     for (int i = s.length() - 1; i >= 0; i--) {
858       if (Character.isWhitespace(s.charAt(i))) return i;
859     }
860     return -1;
861   }
862
863   private boolean checkMatchWithingHierarchy(PsiElement el2, SubstitutionHandler handler, PsiElement context) {
864     boolean includeInterfaces = true;
865     boolean includeClasses = true;
866     final PsiElement contextParent = context.getParent();
867
868     if (contextParent instanceof PsiReferenceList) {
869       final PsiElement grandParentContext = contextParent.getParent();
870
871       if (grandParentContext instanceof PsiClass) {
872         final PsiClass psiClass = (PsiClass)grandParentContext;
873
874         if (contextParent == psiClass.getExtendsList()) {
875           includeInterfaces = psiClass.isInterface();
876         }
877         else if (contextParent == psiClass.getImplementsList()) {
878           includeClasses = false;
879         }
880       }
881     }
882
883     // is type2 is (strict) subtype of type
884     final NodeIterator node = new HierarchyNodeIterator(el2, includeClasses, includeInterfaces);
885
886     if (handler.isStrictSubtype()) {
887       node.advance();
888     }
889
890     final boolean notPredicate = handler.getPredicate() instanceof NotPredicate;
891     while (node.hasNext() && !handler.validate(node.current(), 0, -1, myMatchingVisitor.getMatchContext())) {
892       if (notPredicate) return false;
893       node.advance();
894     }
895
896     if (node.hasNext()) {
897       handler.addResult(el2, 0, -1, myMatchingVisitor.getMatchContext());
898       return true;
899     }
900     else {
901       return false;
902     }
903   }
904
905   @Override
906   public void visitConditionalExpression(final PsiConditionalExpression cond) {
907     final PsiConditionalExpression cond2 = (PsiConditionalExpression)myMatchingVisitor.getElement();
908
909     myMatchingVisitor.setResult(myMatchingVisitor.match(cond.getCondition(), cond2.getCondition()) &&
910                                 myMatchingVisitor.matchSons(cond, cond2));
911   }
912
913   @Override
914   public void visitPolyadicExpression(PsiPolyadicExpression expression) {
915     final PsiPolyadicExpression expr2 = (PsiPolyadicExpression)myMatchingVisitor.getElement();
916
917     myMatchingVisitor.setResult(expression.getOperationTokenType().equals(expr2.getOperationTokenType()));
918     if (myMatchingVisitor.getResult()) {
919       final PsiExpression[] operands1 = expression.getOperands();
920       final PsiExpression[] operands2 = expr2.getOperands();
921       myMatchingVisitor.setResult(
922         myMatchingVisitor.matchSequentially(new ArrayBackedNodeIterator(operands1), new ArrayBackedNodeIterator(operands2)));
923     }
924   }
925
926   @Override
927   public void visitVariable(final PsiVariable var) {
928     myMatchingVisitor.getMatchContext().pushResult();
929     final PsiIdentifier nameIdentifier = var.getNameIdentifier();
930
931     final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(nameIdentifier);
932     final PsiVariable var2 = (PsiVariable)myMatchingVisitor.getElement();
933
934     try {
935       myMatchingVisitor.setResult((myMatchingVisitor.matchText(var.getNameIdentifier(), var2.getNameIdentifier()) || isTypedVar) &&
936                                    myMatchingVisitor.match(var.getModifierList(), var2.getModifierList()));
937       if (myMatchingVisitor.getResult()) {
938         final PsiTypeElement typeElement1 = var.getTypeElement();
939         if (typeElement1 != null) {
940           PsiTypeElement typeElement2 = var2.getTypeElement();
941           if (typeElement2 == null) {
942             typeElement2 = JavaPsiFacade.getElementFactory(var2.getProject()).createTypeElement(var2.getType());
943           }
944           myMatchingVisitor.setResult(myMatchingVisitor.match(typeElement1, typeElement2));
945         }
946       }
947
948       if (myMatchingVisitor.getResult()) {
949         // Check initializer
950         final PsiExpression initializer = var.getInitializer();
951         final PsiExpression var2Initializer = var2.getInitializer();
952         myMatchingVisitor.setResult(myMatchingVisitor.match(initializer, var2Initializer));
953       }
954
955       if (myMatchingVisitor.getResult() && var instanceof PsiParameter && var.getParent() instanceof PsiCatchSection) {
956         myMatchingVisitor.setResult(myMatchingVisitor.match(
957           ((PsiCatchSection)var.getParent()).getCatchBlock(),
958           ((PsiCatchSection)var2.getParent()).getCatchBlock()
959         ));
960       }
961     }
962     finally {
963       saveOrDropResult(nameIdentifier, isTypedVar, var2.getNameIdentifier());
964     }
965   }
966
967   private void matchArrayDims(final PsiNewExpression new1, final PsiNewExpression new2) {
968     final PsiExpression[] arrayDims = new1.getArrayDimensions();
969     final PsiExpression[] arrayDims2 = new2.getArrayDimensions();
970
971     if (arrayDims.length == arrayDims2.length && arrayDims.length != 0) {
972       for (int i = 0; i < arrayDims.length; ++i) {
973         myMatchingVisitor.setResult(myMatchingVisitor.match(arrayDims[i], arrayDims2[i]));
974         if (!myMatchingVisitor.getResult()) return;
975       }
976     }
977     else {
978       myMatchingVisitor.setResult((arrayDims == arrayDims2) && myMatchingVisitor.matchSons(new1.getArgumentList(), new2.getArgumentList()));
979     }
980   }
981
982   private void saveOrDropResult(final PsiIdentifier methodNameNode, final boolean typedVar, final PsiIdentifier methodNameNode2) {
983     MatchResultImpl ourResult = myMatchingVisitor.getMatchContext().hasResult() ? myMatchingVisitor.getMatchContext().getResult() : null;
984     myMatchingVisitor.getMatchContext().popResult();
985
986     if (myMatchingVisitor.getResult()) {
987       if (typedVar) {
988         final SubstitutionHandler handler =
989           (SubstitutionHandler)myMatchingVisitor.getMatchContext().getPattern().getHandler(methodNameNode);
990         if (ourResult != null) ourResult.setScopeMatch(true);
991         handler.setNestedResult(ourResult);
992         myMatchingVisitor.setResult(handler.handle(methodNameNode2, myMatchingVisitor.getMatchContext()));
993
994         if (handler.getNestedResult() != null) { // some constraint prevent from adding
995           handler.setNestedResult(null);
996           copyResults(ourResult);
997         }
998       }
999       else if (ourResult != null) {
1000         copyResults(ourResult);
1001       }
1002     }
1003   }
1004
1005   private void matchImplicitQualifier(MatchingHandler matchingHandler, PsiElement target, MatchContext context) {
1006     if (!(matchingHandler instanceof SubstitutionHandler)) {
1007       myMatchingVisitor.setResult(false);
1008       return;
1009     }
1010     final SubstitutionHandler substitutionHandler = (SubstitutionHandler)matchingHandler;
1011     final MatchPredicate predicate = substitutionHandler.getPredicate();
1012     if (substitutionHandler.getMinOccurs() != 0) {
1013       myMatchingVisitor.setResult(false);
1014       return;
1015     }
1016     if (predicate == null) {
1017       myMatchingVisitor.setResult(true);
1018       return;
1019     }
1020     if (target == null) {
1021       myMatchingVisitor.setResult(false);
1022       return;
1023     }
1024     if (target instanceof PsiModifierListOwner && ((PsiModifierListOwner)target).hasModifierProperty(PsiModifier.STATIC)) {
1025       myMatchingVisitor.setResult(predicate.match(null, PsiTreeUtil.getParentOfType(target, PsiClass.class), context));
1026     } else {
1027       final PsiElementFactory factory = JavaPsiFacade.getElementFactory(target.getProject());
1028       final PsiExpression implicitReference = factory.createExpressionFromText("this", target);
1029       myMatchingVisitor.setResult(predicate.match(null, implicitReference, context));
1030     }
1031   }
1032
1033   @Override
1034   public void visitMethodCallExpression(final PsiMethodCallExpression mcall) {
1035     final PsiElement element = myMatchingVisitor.getElement();
1036     if (!(element instanceof PsiMethodCallExpression)) {
1037       myMatchingVisitor.setResult(false);
1038       return;
1039     }
1040     final PsiMethodCallExpression mcall2 = (PsiMethodCallExpression)element;
1041     final PsiReferenceExpression mcallRef1 = mcall.getMethodExpression();
1042     final PsiReferenceExpression mcallRef2 = mcall2.getMethodExpression();
1043
1044     final PsiElement patternMethodName = mcallRef1.getReferenceNameElement();
1045     final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(patternMethodName);
1046
1047     if (!isTypedVar && !myMatchingVisitor.matchText(patternMethodName, mcallRef2.getReferenceNameElement())) {
1048       myMatchingVisitor.setResult(false);
1049       return;
1050     }
1051
1052     final PsiExpression patternQualifier = mcallRef1.getQualifierExpression();
1053     final PsiExpression matchedQualifier = mcallRef2.getQualifierExpression();
1054     if (patternQualifier != null) {
1055
1056       if (matchedQualifier != null) {
1057         myMatchingVisitor.setResult(myMatchingVisitor.match(patternQualifier, matchedQualifier));
1058         if (!myMatchingVisitor.getResult()) return;
1059       }
1060       else {
1061         final PsiMethod method = mcall2.resolveMethod();
1062         if (method != null) {
1063           if (patternQualifier instanceof PsiThisExpression) {
1064             myMatchingVisitor.setResult(!method.hasModifierProperty(PsiModifier.STATIC));
1065             return;
1066           }
1067         }
1068         final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(patternQualifier);
1069         matchImplicitQualifier(handler, method, myMatchingVisitor.getMatchContext());
1070         if (!myMatchingVisitor.getResult()) {
1071           return;
1072         }
1073       }
1074     }
1075     else if (matchedQualifier != null) {
1076       myMatchingVisitor.setResult(false);
1077       return;
1078     }
1079
1080     myMatchingVisitor.setResult(myMatchingVisitor.matchSons(mcall.getArgumentList(), mcall2.getArgumentList()));
1081
1082     if (myMatchingVisitor.getResult()) {
1083       myMatchingVisitor.setResult(matchTypeParameters(mcallRef1, mcallRef2));
1084     }
1085
1086     if (myMatchingVisitor.getResult() && isTypedVar) {
1087       boolean res = myMatchingVisitor.getResult();
1088       res &= myMatchingVisitor.handleTypedElement(patternMethodName, mcallRef2.getReferenceNameElement());
1089       myMatchingVisitor.setResult(res);
1090     }
1091   }
1092
1093   private boolean matchTypeParameters(PsiJavaCodeReferenceElement mcallRef1, PsiJavaCodeReferenceElement mcallRef2) {
1094     final PsiReferenceParameterList patternParameterList = mcallRef1.getParameterList();
1095     if (patternParameterList == null) {
1096       return true;
1097     }
1098     final PsiTypeElement[] patternTypeElements = patternParameterList.getTypeParameterElements();
1099     if (patternTypeElements.length == 0) {
1100       return true;
1101     }
1102     PsiReferenceParameterList matchedParameterList = mcallRef2.getParameterList();
1103     if (matchedParameterList == null) {
1104       return false;
1105     }
1106     if (matchedParameterList.getFirstChild() == null) { // check inferred type parameters
1107       final JavaResolveResult resolveResult = mcallRef2.advancedResolve(false);
1108       final PsiMethod targetMethod = (PsiMethod)resolveResult.getElement();
1109       if (targetMethod == null) {
1110         return false;
1111       }
1112       final PsiTypeParameterList typeParameterList = targetMethod.getTypeParameterList();
1113       if (typeParameterList == null) {
1114         return false;
1115       }
1116       final PsiTypeParameter[] typeParameters = typeParameterList.getTypeParameters();
1117       final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
1118       matchedParameterList = (PsiReferenceParameterList)matchedParameterList.copy();
1119       for (final PsiTypeParameter typeParameter : typeParameters) {
1120         final PsiType type = substitutor.substitute(typeParameter);
1121         if (type == null) {
1122           return false;
1123         }
1124         final PsiTypeElement matchedTypeElement = JavaPsiFacade.getElementFactory(mcallRef1.getProject()).createTypeElement(type);
1125         matchedParameterList.add(matchedTypeElement);
1126       }
1127     }
1128     final PsiTypeElement[] matchedTypeElements = matchedParameterList.getTypeParameterElements();
1129     return myMatchingVisitor.matchSequentially(patternTypeElements, matchedTypeElements);
1130   }
1131
1132   @Override
1133   public void visitExpressionStatement(final PsiExpressionStatement expr) {
1134     final PsiElement other = myMatchingVisitor.getElement();
1135     if (other instanceof PsiExpressionStatement) {
1136       final PsiExpressionStatement expr2 = (PsiExpressionStatement)other;
1137       myMatchingVisitor.setResult(myMatchingVisitor.match(expr.getExpression(), expr2.getExpression()));
1138     }
1139     else {
1140       myMatchingVisitor.setResult(false);
1141     }
1142   }
1143
1144   @Override
1145   public void visitLiteralExpression(final PsiLiteralExpression const1) {
1146     final PsiLiteralExpression const2 = (PsiLiteralExpression)myMatchingVisitor.getElement();
1147
1148     final MatchingHandler handler = (MatchingHandler)const1.getUserData(CompiledPattern.HANDLER_KEY);
1149     if (handler instanceof SubstitutionHandler) {
1150       final PsiType type1 = const1.getType();
1151       if (type1 != null && !type1.equals(const2.getType())) {
1152         myMatchingVisitor.setResult(false);
1153       }
1154       else {
1155         int offset = 0;
1156         int length = const2.getTextLength();
1157         final String text = const2.getText();
1158
1159         if (length > 2 && text.charAt(0) == '"' && text.charAt(length - 1) == '"') {
1160           length--;
1161           offset++;
1162         }
1163         myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(const2, offset, length, myMatchingVisitor.getMatchContext()));
1164       }
1165     }
1166     else if (handler != null) {
1167       myMatchingVisitor.setResult(handler.match(const1, const2, myMatchingVisitor.getMatchContext()));
1168     }
1169     else {
1170       myMatchingVisitor.setResult(myMatchingVisitor.matchText(const1, const2));
1171     }
1172   }
1173
1174   @Override
1175   public void visitAssignmentExpression(final PsiAssignmentExpression assign) {
1176     final PsiElement other = myMatchingVisitor.getElement();
1177     if (other instanceof PsiAssignmentExpression) {
1178       final PsiAssignmentExpression assign2 = (PsiAssignmentExpression)other;
1179
1180       myMatchingVisitor.setResult(assign.getOperationTokenType().equals(assign2.getOperationTokenType()) &&
1181                                   myMatchingVisitor.match(assign.getLExpression(), assign2.getLExpression()) &&
1182                                   myMatchingVisitor.match(assign.getRExpression(), assign2.getRExpression()));
1183     }
1184     else {
1185       myMatchingVisitor.setResult(false);
1186     }
1187   }
1188
1189   @Override
1190   public void visitIfStatement(final PsiIfStatement if1) {
1191     final PsiIfStatement if2 = (PsiIfStatement)myMatchingVisitor.getElement();
1192
1193     final PsiStatement elseBranch = if1.getElseBranch();
1194     myMatchingVisitor.setResult(myMatchingVisitor.match(if1.getCondition(), if2.getCondition()) &&
1195                                 compareBody(if1.getThenBranch(), if2.getThenBranch()) &&
1196                                 (elseBranch == null || compareBody(elseBranch, if2.getElseBranch())));
1197   }
1198
1199   @Override
1200   public void visitSwitchStatement(final PsiSwitchStatement switch1) {
1201     final PsiSwitchStatement switch2 = (PsiSwitchStatement)myMatchingVisitor.getElement();
1202
1203     myMatchingVisitor.setResult(myMatchingVisitor.match(switch1.getExpression(), switch2.getExpression()) &&
1204                                 myMatchingVisitor.matchSons(switch1.getBody(), switch2.getBody()));
1205   }
1206
1207   @Override
1208   public void visitForStatement(final PsiForStatement for1) {
1209     final PsiForStatement for2 = (PsiForStatement)myMatchingVisitor.getElement();
1210
1211     final PsiStatement initialization = for1.getInitialization();
1212     MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(initialization);
1213
1214     myMatchingVisitor.setResult(handler.match(initialization, for2.getInitialization(), myMatchingVisitor.getMatchContext()) &&
1215                                 myMatchingVisitor.match(for1.getCondition(), for2.getCondition()) &&
1216                                 myMatchingVisitor.match(for1.getUpdate(), for2.getUpdate()) &&
1217                                 compareBody(for1.getBody(), for2.getBody()));
1218   }
1219
1220   @Override
1221   public void visitForeachStatement(PsiForeachStatement for1) {
1222     final PsiForeachStatement for2 = (PsiForeachStatement)myMatchingVisitor.getElement();
1223
1224     myMatchingVisitor.setResult(myMatchingVisitor.match(for1.getIterationParameter(), for2.getIterationParameter()) &&
1225                                 myMatchingVisitor.match(for1.getIteratedValue(), for2.getIteratedValue()) &&
1226                                 compareBody(for1.getBody(), for2.getBody()));
1227   }
1228
1229   @Override
1230   public void visitWhileStatement(final PsiWhileStatement while1) {
1231     final PsiWhileStatement while2 = (PsiWhileStatement)myMatchingVisitor.getElement();
1232
1233     myMatchingVisitor.setResult(myMatchingVisitor.match(while1.getCondition(), while2.getCondition()) &&
1234                                 compareBody(while1.getBody(), while2.getBody()));
1235   }
1236
1237   @Override
1238   public void visitBlockStatement(final PsiBlockStatement block) {
1239     final PsiElement other = myMatchingVisitor.getElement();
1240     if (other instanceof PsiCodeBlock) {
1241       myMatchingVisitor.setResult(!(other.getParent() instanceof PsiBlockStatement) &&
1242                                   myMatchingVisitor.matchSons(block.getCodeBlock(), other));
1243     }
1244     else {
1245       final PsiBlockStatement block2 = (PsiBlockStatement)other;
1246       myMatchingVisitor.setResult(myMatchingVisitor.matchSons(block, block2));
1247     }
1248   }
1249
1250   @Override
1251   public void visitDeclarationStatement(final PsiDeclarationStatement dcl) {
1252     final PsiDeclarationStatement declaration = (PsiDeclarationStatement)myMatchingVisitor.getElement();
1253     myMatchingVisitor.setResult(myMatchingVisitor.matchInAnyOrder(dcl.getDeclaredElements(), declaration.getDeclaredElements()));
1254   }
1255
1256   @Override
1257   public void visitDoWhileStatement(final PsiDoWhileStatement while1) {
1258     final PsiDoWhileStatement while2 = (PsiDoWhileStatement)myMatchingVisitor.getElement();
1259
1260     myMatchingVisitor.setResult(myMatchingVisitor.match(while1.getCondition(), while2.getCondition()) &&
1261                                 compareBody(while1.getBody(), while2.getBody()));
1262   }
1263
1264   @Override
1265   public void visitReturnStatement(final PsiReturnStatement return1) {
1266     final PsiReturnStatement return2 = (PsiReturnStatement)myMatchingVisitor.getElement();
1267
1268     myMatchingVisitor.setResult(myMatchingVisitor.match(return1.getReturnValue(), return2.getReturnValue()));
1269   }
1270
1271   @Override
1272   public void visitPostfixExpression(final PsiPostfixExpression postfix) {
1273     final PsiPostfixExpression postfix2 = (PsiPostfixExpression)myMatchingVisitor.getElement();
1274
1275     myMatchingVisitor.setResult(postfix.getOperationTokenType().equals(postfix2.getOperationTokenType())
1276                                 && myMatchingVisitor.match(postfix.getOperand(), postfix2.getOperand()));
1277   }
1278
1279   @Override
1280   public void visitPrefixExpression(final PsiPrefixExpression prefix) {
1281     final PsiPrefixExpression prefix2 = (PsiPrefixExpression)myMatchingVisitor.getElement();
1282
1283     myMatchingVisitor.setResult(prefix.getOperationTokenType().equals(prefix2.getOperationTokenType())
1284                                 && myMatchingVisitor.match(prefix.getOperand(), prefix2.getOperand()));
1285   }
1286
1287   @Override
1288   public void visitAssertStatement(final PsiAssertStatement assert1) {
1289     final PsiAssertStatement assert2 = (PsiAssertStatement)myMatchingVisitor.getElement();
1290
1291     myMatchingVisitor.setResult(myMatchingVisitor.match(assert1.getAssertCondition(), assert2.getAssertCondition()) &&
1292                                 myMatchingVisitor.match(assert1.getAssertDescription(), assert2.getAssertDescription()));
1293   }
1294
1295   @Override
1296   public void visitBreakStatement(final PsiBreakStatement break1) {
1297     final PsiBreakStatement break2 = (PsiBreakStatement)myMatchingVisitor.getElement();
1298
1299     myMatchingVisitor.setResult(myMatchingVisitor.match(break1.getLabelIdentifier(), break2.getLabelIdentifier()));
1300   }
1301
1302   @Override
1303   public void visitContinueStatement(final PsiContinueStatement continue1) {
1304     final PsiContinueStatement continue2 = (PsiContinueStatement)myMatchingVisitor.getElement();
1305
1306     myMatchingVisitor.setResult(myMatchingVisitor.match(continue1.getLabelIdentifier(), continue2.getLabelIdentifier()));
1307   }
1308
1309   @Override
1310   public void visitSuperExpression(final PsiSuperExpression super1) {
1311     myMatchingVisitor.setResult(true);
1312   }
1313
1314   @Override
1315   public void visitThisExpression(final PsiThisExpression this1) {
1316     myMatchingVisitor.setResult(myMatchingVisitor.getElement() instanceof PsiThisExpression);
1317   }
1318
1319   @Override
1320   public void visitSynchronizedStatement(final PsiSynchronizedStatement synchronized1) {
1321     final PsiSynchronizedStatement synchronized2 = (PsiSynchronizedStatement)myMatchingVisitor.getElement();
1322
1323     myMatchingVisitor.setResult(myMatchingVisitor.match(synchronized1.getLockExpression(), synchronized2.getLockExpression()) &&
1324                                 myMatchingVisitor.matchSons(synchronized1.getBody(), synchronized2.getBody()));
1325   }
1326
1327   @Override
1328   public void visitThrowStatement(final PsiThrowStatement throw1) {
1329     final PsiThrowStatement throw2 = (PsiThrowStatement)myMatchingVisitor.getElement();
1330
1331     myMatchingVisitor.setResult(myMatchingVisitor.match(throw1.getException(), throw2.getException()));
1332   }
1333
1334   @Override
1335   public void visitParenthesizedExpression(PsiParenthesizedExpression expr) {
1336     if (myMatchingVisitor.getElement() instanceof PsiParenthesizedExpression) {
1337       myMatchingVisitor.setResult(myMatchingVisitor.matchSons(expr, myMatchingVisitor.getElement()));
1338     }
1339     else {
1340       myMatchingVisitor.setResult(false);
1341     }
1342   }
1343
1344   @Override
1345   public void visitCatchSection(final PsiCatchSection section) {
1346     final PsiCatchSection section2 = (PsiCatchSection)myMatchingVisitor.getElement();
1347     final PsiParameter parameter = section.getParameter();
1348     if (parameter != null) {
1349       myMatchingVisitor.setResult(myMatchingVisitor.match(parameter, section2.getParameter()));
1350     }
1351     else {
1352       myMatchingVisitor.setResult(myMatchingVisitor.match(section.getCatchBlock(), section2.getCatchBlock()));
1353     }
1354   }
1355
1356   @Override
1357   public void visitTryStatement(final PsiTryStatement try1) {
1358     final PsiTryStatement try2 = (PsiTryStatement)myMatchingVisitor.getElement();
1359
1360     myMatchingVisitor.setResult(myMatchingVisitor.matchSons(try1.getTryBlock(), try2.getTryBlock()));
1361     if (!myMatchingVisitor.getResult()) return;
1362
1363     final PsiResourceList resourceList1 = try1.getResourceList();
1364     final PsiCatchSection[] catches1 = try1.getCatchSections();
1365     final PsiCodeBlock finally1 = try1.getFinallyBlock();
1366
1367     final PsiResourceList resourceList2 = try2.getResourceList();
1368     final PsiCatchSection[] catches2 = try2.getCatchSections();
1369     final PsiCodeBlock finally2 = try2.getFinallyBlock();
1370
1371     if (!myMatchingVisitor.getMatchContext().getOptions().isLooseMatching() &&
1372         ((catches1.length == 0 && catches2.length != 0) ||
1373          (finally1 == null && finally2 != null) ||
1374          (resourceList1 == null && resourceList2 != null)) ||
1375         catches2.length < catches1.length
1376       ) {
1377       myMatchingVisitor.setResult(false);
1378     }
1379     else {
1380       final List<PsiElement> unmatchedElements = new ArrayList<PsiElement>();
1381
1382       if (resourceList1 != null) {
1383         if (resourceList2 == null) {
1384           myMatchingVisitor.setResult(false);
1385           return;
1386         }
1387         final List<PsiResourceListElement> resources1 = PsiTreeUtil.getChildrenOfTypeAsList(resourceList1, PsiResourceListElement.class);
1388         final List<PsiResourceListElement> resources2 = PsiTreeUtil.getChildrenOfTypeAsList(resourceList2, PsiResourceListElement.class);
1389         myMatchingVisitor.setResult(myMatchingVisitor.matchInAnyOrder(
1390           resources1.toArray(new PsiResourceListElement[resources1.size()]),
1391           resources2.toArray(new PsiResourceListElement[resources2.size()])));
1392         if (!myMatchingVisitor.getResult()) return;
1393       }
1394       else if (resourceList2 != null){
1395         unmatchedElements.add(resourceList2);
1396       }
1397
1398       ContainerUtil.addAll(unmatchedElements, catches2);
1399       for (PsiCatchSection catchSection : catches1) {
1400         final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(catchSection);
1401         final PsiElement pinnedNode = handler.getPinnedNode(null);
1402
1403         if (pinnedNode != null) {
1404           myMatchingVisitor.setResult(handler.match(catchSection, pinnedNode, myMatchingVisitor.getMatchContext()));
1405           if (!myMatchingVisitor.getResult()) return;
1406         }
1407         else {
1408           boolean matched = false;
1409           for (int j = 0; j < unmatchedElements.size(); ++j) {
1410             if (handler.match(catchSection, unmatchedElements.get(j), myMatchingVisitor.getMatchContext())) {
1411               unmatchedElements.remove(j);
1412               matched = true;
1413               break;
1414             }
1415           }
1416           if (!matched) {
1417             myMatchingVisitor.setResult(false);
1418             return;
1419           }
1420         }
1421       }
1422
1423       if (finally1 != null) {
1424         myMatchingVisitor.setResult(myMatchingVisitor.matchSons(finally1, finally2));
1425       } else if (finally2 != null) {
1426         unmatchedElements.add(finally2);
1427       }
1428
1429       if (myMatchingVisitor.getResult() && unmatchedElements.size() > 0) {
1430         try2.putUserData(GlobalMatchingVisitor.UNMATCHED_ELEMENTS_KEY, unmatchedElements);
1431       }
1432     }
1433   }
1434
1435   @Override
1436   public void visitSwitchLabelStatement(final PsiSwitchLabelStatement case1) {
1437     final PsiSwitchLabelStatement case2 = (PsiSwitchLabelStatement)myMatchingVisitor.getElement();
1438
1439     myMatchingVisitor.setResult(case1.isDefaultCase() == case2.isDefaultCase() &&
1440                                 myMatchingVisitor.match(case1.getCaseValue(), case2.getCaseValue()));
1441   }
1442
1443   @Override
1444   public void visitInstanceOfExpression(final PsiInstanceOfExpression instanceOf) {
1445     final PsiElement other = myMatchingVisitor.getElement();
1446     if (other instanceof PsiInstanceOfExpression) {
1447       final PsiInstanceOfExpression instanceOf2 = (PsiInstanceOfExpression)other;
1448       myMatchingVisitor.setResult(myMatchingVisitor.match(instanceOf.getOperand(), instanceOf2.getOperand()));
1449       if (myMatchingVisitor.getResult()) {
1450         final PsiTypeElement checkType = instanceOf.getCheckType();
1451         if (checkType != null) {
1452           myMatchingVisitor.setResult(myMatchingVisitor.match(checkType, instanceOf2.getCheckType()));
1453         }
1454       }
1455     }
1456     else {
1457       myMatchingVisitor.setResult(false);
1458     }
1459   }
1460
1461   @Override
1462   public void visitNewExpression(final PsiNewExpression new1) {
1463     final PsiElement other = myMatchingVisitor.getElement();
1464     final PsiJavaCodeReferenceElement classReference = new1.getClassReference();
1465     if (other instanceof PsiArrayInitializerExpression &&
1466         other.getParent() instanceof PsiVariable &&
1467         new1.getArrayDimensions().length == 0 &&
1468         new1.getArrayInitializer() != null
1469       ) {
1470       final MatchContext matchContext = myMatchingVisitor.getMatchContext();
1471       final MatchingHandler handler = matchContext.getPattern().getHandler(classReference);
1472       final boolean looseMatching = myMatchingVisitor.getMatchContext().getOptions().isLooseMatching();
1473       if ((handler instanceof SubstitutionHandler && ((SubstitutionHandler)handler).getMinOccurs() != 0) || !looseMatching) {
1474         myMatchingVisitor.setResult(false);
1475         return;
1476       }
1477       final PsiType otherType = ((PsiArrayInitializerExpression)other).getType();
1478       if (handler instanceof SubstitutionHandler && otherType != null) {
1479         final PsiElementFactory factory = JavaPsiFacade.getElementFactory(other.getProject());
1480         final PsiTypeElement otherTypeElement = factory.createTypeElement(otherType.getDeepComponentType());
1481         final SubstitutionHandler substitutionHandler = (SubstitutionHandler)handler;
1482         final MatchPredicate predicate = substitutionHandler.getPredicate();
1483         myMatchingVisitor.setResult(predicate == null || predicate.match(null, otherTypeElement, matchContext));
1484       }
1485       else {
1486         final PsiType type = new1.getType();
1487         myMatchingVisitor.setResult(type != null && type.equals(otherType));
1488       }
1489       if (myMatchingVisitor.getResult()) {
1490         myMatchingVisitor.matchSons(new1.getArrayInitializer(), other);
1491       }
1492       return;
1493     }
1494
1495     if (!(other instanceof PsiNewExpression)) {
1496       myMatchingVisitor.setResult(false);
1497       return;
1498     }
1499     final PsiNewExpression new2 = (PsiNewExpression)other;
1500
1501     if (classReference != null) {
1502       if (new2.getClassReference() != null) {
1503         myMatchingVisitor.setResult(myMatchingVisitor.match(classReference, new2.getClassReference()) &&
1504                                     myMatchingVisitor.matchSons(new1.getArrayInitializer(), new2.getArrayInitializer()));
1505
1506         if (myMatchingVisitor.getResult()) {
1507           // matching dims
1508           matchArrayDims(new1, new2);
1509         }
1510         return;
1511       }
1512       else {
1513         // match array of primitive by new 'T();
1514         final PsiKeyword newKeyword = PsiTreeUtil.getChildOfType(new2, PsiKeyword.class);
1515         final PsiElement element = PsiTreeUtil.getNextSiblingOfType(newKeyword, PsiWhiteSpace.class);
1516
1517         if (element != null && element.getNextSibling() instanceof PsiKeyword) {
1518           ((LexicalNodesFilter)LexicalNodesFilter.getInstance()).setCareKeyWords(true);
1519
1520           myMatchingVisitor.setResult(myMatchingVisitor.match(classReference, element.getNextSibling()) &&
1521                                       myMatchingVisitor.matchSons(new1.getArrayInitializer(), new2.getArrayInitializer()));
1522
1523           ((LexicalNodesFilter)LexicalNodesFilter.getInstance()).setCareKeyWords(false);
1524           if (myMatchingVisitor.getResult()) {
1525             // matching dims
1526             matchArrayDims(new1, new2);
1527           }
1528
1529           return;
1530         }
1531       }
1532     }
1533
1534     if (classReference == new2.getClassReference()) {
1535       // probably anonymous class or array of primitive type
1536       ((LexicalNodesFilter)LexicalNodesFilter.getInstance()).setCareKeyWords(true);
1537       myMatchingVisitor.setResult(myMatchingVisitor.matchSons(new1, new2));
1538       ((LexicalNodesFilter)LexicalNodesFilter.getInstance()).setCareKeyWords(false);
1539     }
1540     else if (new1.getAnonymousClass() == null &&
1541              classReference != null &&
1542              new2.getAnonymousClass() != null) {
1543       // allow matching anonymous class without pattern
1544       myMatchingVisitor.setResult(myMatchingVisitor.match(classReference, new2.getAnonymousClass().getBaseClassReference()) &&
1545                                   myMatchingVisitor.matchSons(new1.getArgumentList(), new2.getArgumentList()));
1546     }
1547     else {
1548       myMatchingVisitor.setResult(false);
1549     }
1550   }
1551
1552   @Override
1553   public void visitKeyword(PsiKeyword keyword) {
1554     myMatchingVisitor.setResult(myMatchingVisitor.matchText(keyword, myMatchingVisitor.getElement()));
1555   }
1556
1557   @Override
1558   public void visitTypeCastExpression(final PsiTypeCastExpression cast) {
1559     final PsiElement other = myMatchingVisitor.getElement();
1560     if (other instanceof PsiTypeCastExpression) {
1561       final PsiTypeCastExpression cast2 = (PsiTypeCastExpression)other;
1562       myMatchingVisitor.setResult(myMatchingVisitor.match(cast.getCastType(), cast2.getCastType()) &&
1563                                   myMatchingVisitor.match(cast.getOperand(), cast2.getOperand()));
1564     }
1565     else {
1566       myMatchingVisitor.setResult(false);
1567     }
1568   }
1569
1570   @Override
1571   public void visitClassObjectAccessExpression(final PsiClassObjectAccessExpression expr) {
1572     final PsiElement other = myMatchingVisitor.getElement();
1573     if (other instanceof PsiClassObjectAccessExpression) {
1574       final PsiClassObjectAccessExpression expr2 = (PsiClassObjectAccessExpression)other;
1575       myMatchingVisitor.setResult(myMatchingVisitor.match(expr.getOperand(), expr2.getOperand()));
1576     }
1577     else {
1578       myMatchingVisitor.setResult(false);
1579     }
1580   }
1581
1582   @Override
1583   public void visitReferenceElement(final PsiJavaCodeReferenceElement ref) {
1584     final PsiElement other = myMatchingVisitor.getElement();
1585     final PsiAnnotation[] annotations = PsiTreeUtil.getChildrenOfType(ref, PsiAnnotation.class);
1586     if (annotations != null) {
1587       final PsiAnnotation[] otherAnnotations = PsiTreeUtil.getChildrenOfType(other, PsiAnnotation.class);
1588       myMatchingVisitor.setResult(otherAnnotations != null && myMatchingVisitor.matchInAnyOrder(annotations, otherAnnotations));
1589       if (!myMatchingVisitor.getResult()) return;
1590     }
1591     myMatchingVisitor.setResult(matchType(ref, other));
1592   }
1593
1594   @Override
1595   public void visitTypeElement(final PsiTypeElement typeElement) {
1596     final PsiElement other = myMatchingVisitor.getElement(); // might not be a PsiTypeElement
1597
1598     final PsiAnnotation[] annotations = PsiTreeUtil.getChildrenOfType(typeElement, PsiAnnotation.class);
1599     // also can't use AnnotationOwner api because it is not implemented completely yet (see e.g. ClsTypeParameterImpl)
1600     final PsiAnnotation[] annotations2 = PsiTreeUtil.getChildrenOfType(other, PsiAnnotation.class);
1601     if (annotations != null) {
1602       myMatchingVisitor.setResult(annotations2 != null && myMatchingVisitor.matchInAnyOrder(annotations, annotations2));
1603       if (!myMatchingVisitor.getResult()) return;
1604     }
1605     final PsiTypeElement[] typeElementChildren = PsiTreeUtil.getChildrenOfType(typeElement, PsiTypeElement.class);
1606     if (typeElementChildren != null && typeElementChildren.length > 1) {
1607       // multi catch type element
1608       final PsiTypeElement[] typeElementChildren2 = PsiTreeUtil.getChildrenOfType(other, PsiTypeElement.class);
1609       myMatchingVisitor.setResult(
1610         typeElementChildren2 != null && myMatchingVisitor.matchInAnyOrder(typeElementChildren, typeElementChildren2));
1611     }
1612     else {
1613       myMatchingVisitor.setResult(matchType(typeElement, other));
1614     }
1615   }
1616
1617   @Override
1618   public void visitTypeParameter(PsiTypeParameter psiTypeParameter) {
1619     final PsiTypeParameter parameter = (PsiTypeParameter)myMatchingVisitor.getElement();
1620     final PsiIdentifier identifier = psiTypeParameter.getNameIdentifier();
1621     final PsiIdentifier identifier2 = parameter.getNameIdentifier();
1622
1623     final MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(identifier);
1624     if (handler instanceof SubstitutionHandler) {
1625       myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(identifier2, myMatchingVisitor.getMatchContext()));
1626     }
1627     else {
1628       myMatchingVisitor.setResult(myMatchingVisitor.matchText(identifier, identifier2));
1629     }
1630
1631     if (myMatchingVisitor.getResult()) {
1632       myMatchingVisitor.setResult(matchInAnyOrder(psiTypeParameter.getExtendsList(), parameter.getExtendsList()));
1633     }
1634     if (myMatchingVisitor.getResult()) {
1635       myMatchingVisitor.setResult(myMatchingVisitor.matchInAnyOrder(psiTypeParameter.getAnnotations(), parameter.getAnnotations()));
1636     }
1637   }
1638
1639   @Override
1640   public void visitClass(PsiClass clazz) {
1641     if (clazz.hasTypeParameters()) {
1642       myMatchingVisitor
1643         .setResult(
1644           myMatchingVisitor.match(clazz.getTypeParameterList(), ((PsiClass)myMatchingVisitor.getElement()).getTypeParameterList()));
1645
1646       if (!myMatchingVisitor.getResult()) return;
1647     }
1648
1649     PsiClass clazz2;
1650
1651     if (myMatchingVisitor.getElement() instanceof PsiDeclarationStatement &&
1652         myMatchingVisitor.getElement().getFirstChild() instanceof PsiClass
1653       ) {
1654       clazz2 = (PsiClass)myMatchingVisitor.getElement().getFirstChild();
1655     }
1656     else {
1657       clazz2 = (PsiClass)myMatchingVisitor.getElement();
1658     }
1659
1660     final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(clazz.getNameIdentifier());
1661
1662     if (clazz.getModifierList().getTextLength() > 0) {
1663       if (!myMatchingVisitor.match(clazz.getModifierList(), clazz2.getModifierList())) {
1664         myMatchingVisitor.setResult(false);
1665         return;
1666       }
1667     }
1668
1669     myMatchingVisitor.setResult((myMatchingVisitor.matchText(clazz.getNameIdentifier(), clazz2.getNameIdentifier()) || isTypedVar) &&
1670                                 compareClasses(clazz, clazz2));
1671
1672     if (myMatchingVisitor.getResult() && isTypedVar) {
1673       PsiElement id = clazz2.getNameIdentifier();
1674       if (id == null) id = clazz2;
1675       myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(clazz.getNameIdentifier(), id));
1676     }
1677   }
1678
1679   @Override
1680   public void visitTypeParameterList(PsiTypeParameterList psiTypeParameterList) {
1681     myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(
1682       psiTypeParameterList.getFirstChild(),
1683       myMatchingVisitor.getElement().getFirstChild()
1684     ));
1685   }
1686
1687   @Override
1688   public void visitMethod(PsiMethod method) {
1689     final PsiIdentifier methodNameNode = method.getNameIdentifier();
1690     final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(methodNameNode);
1691     final PsiMethod method2 = (PsiMethod)myMatchingVisitor.getElement();
1692
1693     myMatchingVisitor.getMatchContext().pushResult();
1694
1695     try {
1696       final PsiDocComment docComment = method.getDocComment();
1697       if (docComment != null) {
1698         myMatchingVisitor.setResult(myMatchingVisitor.match(docComment, method2));
1699         if (!myMatchingVisitor.getResult()) return;
1700       }
1701       if (method.hasTypeParameters()) {
1702         myMatchingVisitor.setResult(
1703           myMatchingVisitor.match(method.getTypeParameterList(), ((PsiMethod)myMatchingVisitor.getElement()).getTypeParameterList()));
1704
1705         if (!myMatchingVisitor.getResult()) return;
1706       }
1707
1708       if (!checkHierarchy(method2, method)) {
1709         myMatchingVisitor.setResult(false);
1710         return;
1711       }
1712
1713       myMatchingVisitor.setResult((myMatchingVisitor.matchText(method.getNameIdentifier(), method2.getNameIdentifier()) || isTypedVar) &&
1714                                   myMatchingVisitor.match(method.getModifierList(), method2.getModifierList()) &&
1715                                   myMatchingVisitor.matchSons(method.getParameterList(), method2.getParameterList()) &&
1716                                   myMatchingVisitor.match(method.getReturnTypeElement(), method2.getReturnTypeElement()) &&
1717                                                     matchInAnyOrder(method.getThrowsList(), method2.getThrowsList()) &&
1718                                   myMatchingVisitor.matchSonsOptionally(method.getBody(), method2.getBody()));
1719     }
1720     finally {
1721       saveOrDropResult(methodNameNode, isTypedVar, method2.getNameIdentifier());
1722     }
1723   }
1724 }