Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / RecursionWeigher.java
1 /*
2  * Copyright 2000-2017 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.codeInsight.completion;
17
18 import com.intellij.codeInsight.ExpectedTypeInfo;
19 import com.intellij.codeInsight.JavaPsiEquivalenceUtil;
20 import com.intellij.codeInsight.lookup.LookupElement;
21 import com.intellij.codeInsight.lookup.LookupElementWeigher;
22 import com.intellij.openapi.util.Comparing;
23 import com.intellij.patterns.PsiJavaPatterns;
24 import com.intellij.patterns.StandardPatterns;
25 import com.intellij.psi.*;
26 import com.intellij.psi.filters.AndFilter;
27 import com.intellij.psi.filters.ClassFilter;
28 import com.intellij.psi.filters.ElementFilter;
29 import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
30 import com.intellij.psi.filters.element.ExcludeSillyAssignment;
31 import com.intellij.psi.impl.search.MethodDeepestSuperSearcher;
32 import com.intellij.psi.scope.ElementClassFilter;
33 import com.intellij.psi.util.PropertyUtil;
34 import com.intellij.psi.util.PropertyUtilBase;
35 import com.intellij.psi.util.PsiTreeUtil;
36 import com.intellij.util.CommonProcessors;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 /**
41 * @author peter
42 */
43 class RecursionWeigher extends LookupElementWeigher {
44   private final ElementFilter myFilter;
45   private final PsiElement myPosition;
46   private final PsiReferenceExpression myReference;
47   @Nullable private final PsiMethodCallExpression myExpression;
48   private final PsiMethod myPositionMethod;
49   private final ExpectedTypeInfo[] myExpectedInfos;
50   private final PsiExpression myCallQualifier;
51   private final PsiExpression myPositionQualifier;
52   private final boolean myDelegate;
53   private final CompletionType myCompletionType;
54
55   RecursionWeigher(PsiElement position,
56                           CompletionType completionType,
57                           @NotNull PsiReferenceExpression reference,
58                           @Nullable PsiMethodCallExpression expression,
59                           ExpectedTypeInfo[] expectedInfos) {
60     super("recursion");
61     myCompletionType = completionType;
62     myFilter = recursionFilter(position);
63     myPosition = position;
64     myReference = reference;
65     myExpression = expression;
66     myPositionMethod = PsiTreeUtil.getParentOfType(position, PsiMethod.class, false);
67     myExpectedInfos = expectedInfos;
68     myCallQualifier = normalizeQualifier(myReference.getQualifierExpression());
69     myPositionQualifier = normalizeQualifier(position.getParent() instanceof PsiJavaCodeReferenceElement
70                                              ? ((PsiJavaCodeReferenceElement)position.getParent()).getQualifier()
71                                              : null);
72     myDelegate = isDelegatingCall();
73   }
74
75   @Nullable
76   private static PsiExpression normalizeQualifier(@Nullable PsiElement qualifier) {
77     return qualifier instanceof PsiThisExpression || !(qualifier instanceof PsiExpression) ? null : (PsiExpression)qualifier;
78   }
79
80   private boolean isDelegatingCall() {
81     if (myCallQualifier != null &&
82         myPositionQualifier != null &&
83         myCallQualifier != myPositionQualifier &&
84         JavaPsiEquivalenceUtil.areExpressionsEquivalent(myCallQualifier, myPositionQualifier)) {
85       return false;
86     }
87
88     if (myCallQualifier == null && myPositionQualifier == null) {
89       return false;
90     }
91
92     return true;
93   }
94
95   @Nullable
96   static ElementFilter recursionFilter(PsiElement element) {
97     if (PsiJavaPatterns.psiElement().afterLeaf(PsiKeyword.RETURN).inside(PsiReturnStatement.class).accepts(element)) {
98       return new ExcludeDeclaredFilter(ElementClassFilter.METHOD);
99     }
100
101     if (PsiJavaPatterns.psiElement().inside(
102       StandardPatterns.or(
103         PsiJavaPatterns.psiElement(PsiAssignmentExpression.class),
104         PsiJavaPatterns.psiElement(PsiVariable.class))).
105         andNot(PsiJavaPatterns.psiElement().afterLeaf(".")).accepts(element)) {
106       return new AndFilter(new ExcludeSillyAssignment(),
107                                                    new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class)));
108     }
109     return null;
110   }
111
112   private enum Result {
113     delegation,
114     normal,
115     passingObjectToItself,
116     recursive,
117   }
118
119   @NotNull
120   @Override
121   public Result weigh(@NotNull LookupElement element) {
122     final Object object = element.getObject();
123     if (!(object instanceof PsiMethod || object instanceof PsiVariable || object instanceof PsiExpression)) return Result.normal;
124
125     if (myFilter != null && !myFilter.isAcceptable(object, myPosition)) {
126       return Result.recursive;
127     }
128
129     if (isPassingObjectToItself(object) && myCompletionType == CompletionType.SMART) {
130       return Result.passingObjectToItself;
131     }
132
133     if (myExpectedInfos != null) {
134       final PsiType itemType = JavaCompletionUtil.getLookupElementType(element);
135       if (itemType != null) {
136         boolean hasRecursiveInvocations = false;
137         boolean hasOtherInvocations = false;
138
139         for (final ExpectedTypeInfo expectedInfo : myExpectedInfos) {
140           PsiMethod calledMethod = expectedInfo.getCalledMethod();
141           if (!expectedInfo.getType().isAssignableFrom(itemType)) continue;
142
143           if (calledMethod != null && calledMethod.equals(myPositionMethod) || isGetterSetterAssignment(object, calledMethod)) {
144             hasRecursiveInvocations = true;
145           } else if (calledMethod != null) {
146             hasOtherInvocations = true;
147           }
148         }
149         if (hasRecursiveInvocations && !hasOtherInvocations) {
150           return myDelegate ? Result.delegation : Result.recursive;
151         }
152       }
153     }
154     if (myExpression != null) {
155       return Result.normal;
156     }
157
158     if (object instanceof PsiMethod && myPositionMethod != null) {
159       final PsiMethod method = (PsiMethod)object;
160       if (PsiTreeUtil.isAncestor(myReference, myPosition, false) &&
161           Comparing.equal(method.getName(), myPositionMethod.getName())) {
162         if (!myDelegate && findDeepestSuper(method).equals(findDeepestSuper(myPositionMethod))) {
163           return Result.recursive;
164         }
165         return Result.delegation;
166       }
167     }
168
169     return Result.normal;
170   }
171
172   @Nullable
173   private String getSetterPropertyName(@Nullable PsiMethod calledMethod) {
174     if (PropertyUtilBase.isSimplePropertySetter(calledMethod)) {
175       assert calledMethod != null;
176       return PropertyUtilBase.getPropertyName(calledMethod);
177     }
178     PsiReferenceExpression reference = ExcludeSillyAssignment.getAssignedReference(myPosition);
179     if (reference != null) {
180       PsiElement target = reference.resolve();
181       if (target instanceof PsiField) {
182         return PropertyUtilBase.suggestPropertyName((PsiField)target);
183       }
184     }
185     return null;
186   }
187
188   private boolean isGetterSetterAssignment(Object lookupObject, @Nullable PsiMethod calledMethod) {
189     String prop = getSetterPropertyName(calledMethod);
190     if (prop == null) return false;
191
192     if (lookupObject instanceof PsiField &&
193         prop.equals(PropertyUtilBase.suggestPropertyName((PsiField)lookupObject))) {
194       return true;
195     }
196     if (lookupObject instanceof PsiMethod &&
197         PropertyUtilBase.isSimplePropertyGetter((PsiMethod)lookupObject) &&
198         prop.equals(PropertyUtilBase.getPropertyName((PsiMethod)lookupObject))) {
199       return true;
200     }
201     return false;
202   }
203
204   private boolean isPassingObjectToItself(Object object) {
205     if (object instanceof PsiThisExpression) {
206       return myCallQualifier != null && !myDelegate || myCallQualifier instanceof PsiSuperExpression;
207     }
208     return myCallQualifier instanceof PsiReferenceExpression &&
209            object.equals(((PsiReferenceExpression)myCallQualifier).advancedResolve(true).getElement());
210   }
211
212   @NotNull
213   public static PsiMethod findDeepestSuper(@NotNull final PsiMethod method) {
214     CommonProcessors.FindFirstProcessor<PsiMethod> processor = new CommonProcessors.FindFirstProcessor<>();
215     MethodDeepestSuperSearcher.processDeepestSuperMethods(method, processor);
216     final PsiMethod first = processor.getFoundValue();
217     return first == null ? method : first;
218   }
219 }