blob: b61c8a2cae151c7be980c423eb5e06c66ad8a87e [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.JavaPsiEquivalenceUtil;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementWeigher;
import com.intellij.openapi.util.Comparing;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.patterns.StandardPatterns;
import com.intellij.psi.*;
import com.intellij.psi.filters.AndFilter;
import com.intellij.psi.filters.ClassFilter;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
import com.intellij.psi.filters.element.ExcludeSillyAssignment;
import com.intellij.psi.impl.search.MethodDeepestSuperSearcher;
import com.intellij.psi.scope.ElementClassFilter;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.CommonProcessors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author peter
*/
class RecursionWeigher extends LookupElementWeigher {
private final ElementFilter myFilter;
private final PsiElement myPosition;
private final PsiReferenceExpression myReference;
@Nullable private final PsiMethodCallExpression myExpression;
private final PsiMethod myPositionMethod;
private final ExpectedTypeInfo[] myExpectedInfos;
private final PsiExpression myCallQualifier;
private final PsiExpression myPositionQualifier;
private final boolean myDelegate;
private final CompletionType myCompletionType;
public RecursionWeigher(PsiElement position,
CompletionType completionType,
@NotNull PsiReferenceExpression reference,
@Nullable PsiMethodCallExpression expression,
ExpectedTypeInfo[] expectedInfos) {
super("recursion");
myCompletionType = completionType;
myFilter = recursionFilter(position);
myPosition = position;
myReference = reference;
myExpression = expression;
myPositionMethod = PsiTreeUtil.getParentOfType(position, PsiMethod.class, false);
myExpectedInfos = expectedInfos;
myCallQualifier = normalizeQualifier(myReference.getQualifierExpression());
myPositionQualifier = normalizeQualifier(position.getParent() instanceof PsiJavaCodeReferenceElement
? ((PsiJavaCodeReferenceElement)position.getParent()).getQualifier()
: null);
myDelegate = isDelegatingCall();
}
@Nullable
private static PsiExpression normalizeQualifier(@Nullable PsiElement qualifier) {
return qualifier instanceof PsiThisExpression || !(qualifier instanceof PsiExpression) ? null : (PsiExpression)qualifier;
}
private boolean isDelegatingCall() {
if (myCallQualifier != null &&
myPositionQualifier != null &&
myCallQualifier != myPositionQualifier &&
JavaPsiEquivalenceUtil.areExpressionsEquivalent(myCallQualifier, myPositionQualifier)) {
return false;
}
if (myCallQualifier == null && myPositionQualifier == null) {
return false;
}
return true;
}
@Nullable
static ElementFilter recursionFilter(PsiElement element) {
if (PsiJavaPatterns.psiElement().afterLeaf(PsiKeyword.RETURN).inside(PsiReturnStatement.class).accepts(element)) {
return new ExcludeDeclaredFilter(ElementClassFilter.METHOD);
}
if (PsiJavaPatterns.psiElement().inside(
StandardPatterns.or(
PsiJavaPatterns.psiElement(PsiAssignmentExpression.class),
PsiJavaPatterns.psiElement(PsiVariable.class))).
andNot(PsiJavaPatterns.psiElement().afterLeaf(".")).accepts(element)) {
return new AndFilter(new ExcludeSillyAssignment(),
new ExcludeDeclaredFilter(new ClassFilter(PsiVariable.class)));
}
return null;
}
private enum Result {
delegation,
normal,
passingObjectToItself,
recursive,
}
@NotNull
@Override
public Result weigh(@NotNull LookupElement element) {
final Object object = element.getObject();
if (!(object instanceof PsiMethod || object instanceof PsiVariable || object instanceof PsiExpression)) return Result.normal;
if (myFilter != null && !myFilter.isAcceptable(object, myPosition)) {
return Result.recursive;
}
if (isPassingObjectToItself(object) && myCompletionType == CompletionType.SMART) {
return Result.passingObjectToItself;
}
if (myExpectedInfos != null) {
final PsiType itemType = JavaCompletionUtil.getLookupElementType(element);
if (itemType != null) {
boolean hasRecursiveInvocations = false;
boolean hasOtherInvocations = false;
for (final ExpectedTypeInfo expectedInfo : myExpectedInfos) {
PsiMethod calledMethod = expectedInfo.getCalledMethod();
if (!expectedInfo.getType().isAssignableFrom(itemType)) continue;
if (calledMethod != null && calledMethod.equals(myPositionMethod) || isGetterSetterAssignment(object, calledMethod)) {
hasRecursiveInvocations = true;
} else if (calledMethod != null) {
hasOtherInvocations = true;
}
}
if (hasRecursiveInvocations && !hasOtherInvocations) {
return myDelegate ? Result.delegation : Result.recursive;
}
}
}
if (myExpression != null) {
return Result.normal;
}
if (object instanceof PsiMethod && myPositionMethod != null) {
final PsiMethod method = (PsiMethod)object;
if (PsiTreeUtil.isAncestor(myReference, myPosition, false) &&
Comparing.equal(method.getName(), myPositionMethod.getName())) {
if (!myDelegate && findDeepestSuper(method).equals(findDeepestSuper(myPositionMethod))) {
return Result.recursive;
}
return Result.delegation;
}
}
return Result.normal;
}
@Nullable
private String getSetterPropertyName(@Nullable PsiMethod calledMethod) {
if (PropertyUtil.isSimplePropertySetter(calledMethod)) {
assert calledMethod != null;
return PropertyUtil.getPropertyName(calledMethod);
}
PsiReferenceExpression reference = ExcludeSillyAssignment.getAssignedReference(myPosition);
if (reference != null) {
PsiElement target = reference.resolve();
if (target instanceof PsiField) {
return PropertyUtil.suggestPropertyName((PsiField)target);
}
}
return null;
}
private boolean isGetterSetterAssignment(Object lookupObject, @Nullable PsiMethod calledMethod) {
String prop = getSetterPropertyName(calledMethod);
if (prop == null) return false;
if (lookupObject instanceof PsiField &&
prop.equals(PropertyUtil.suggestPropertyName((PsiField)lookupObject))) {
return true;
}
if (lookupObject instanceof PsiMethod &&
PropertyUtil.isSimplePropertyGetter((PsiMethod)lookupObject) &&
prop.equals(PropertyUtil.getPropertyName((PsiMethod)lookupObject))) {
return true;
}
return false;
}
private boolean isPassingObjectToItself(Object object) {
if (object instanceof PsiThisExpression) {
return myCallQualifier != null && !myDelegate || myCallQualifier instanceof PsiSuperExpression;
}
return myCallQualifier instanceof PsiReferenceExpression &&
object.equals(((PsiReferenceExpression)myCallQualifier).advancedResolve(true).getElement());
}
@NotNull
public static PsiMethod findDeepestSuper(@NotNull final PsiMethod method) {
CommonProcessors.FindFirstProcessor<PsiMethod> processor = new CommonProcessors.FindFirstProcessor<PsiMethod>();
MethodDeepestSuperSearcher.processDeepestSuperMethods(method, processor);
final PsiMethod first = processor.getFoundValue();
return first == null ? method : first;
}
}