blob: 311b161df557cb2492613fa140bf2ac421fe4c56 [file] [log] [blame]
/*
* Copyright 2000-2014 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.psi.impl.source.tree.java;
import com.intellij.icons.AllIcons;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.SharedImplUtil;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.scope.ElementClassFilter;
import com.intellij.psi.scope.PsiConflictResolver;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.conflictResolvers.DuplicateConflictResolver;
import com.intellij.psi.scope.processor.FilterScopeProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Map;
public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase implements PsiMethodReferenceExpression {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiMethodReferenceExpressionImpl");
private static final MethodReferenceResolver RESOLVER = new MethodReferenceResolver();
public PsiMethodReferenceExpressionImpl() {
super(JavaElementType.METHOD_REF_EXPRESSION);
}
@Override
public PsiTypeElement getQualifierType() {
final PsiElement qualifier = getQualifier();
return qualifier instanceof PsiTypeElement ? (PsiTypeElement)qualifier : null;
}
@Nullable
@Override
public PsiType getFunctionalInterfaceType() {
return FunctionalInterfaceParameterizationUtil.getGroundTargetType(LambdaUtil.getFunctionalInterfaceType(this, true));
}
@Override
public boolean isExact() {
return getPotentiallyApplicableMember() != null;
}
@Override
public boolean isPotentiallyCompatible(final PsiType functionalInterfaceType) {
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
if (interfaceMethod == null) return false;
final MethodReferenceResolver resolver = new MethodReferenceResolver() {
@Override
protected PsiConflictResolver createResolver(PsiMethodReferenceExpressionImpl referenceExpression,
PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult,
PsiMethod interfaceMethod,
MethodSignature signature) {
return DuplicateConflictResolver.INSTANCE;
}
@Override
protected PsiType getInterfaceType(PsiMethodReferenceExpression reference) {
return functionalInterfaceType;
}
};
final ResolveResult[] result = resolver.resolve(this, getContainingFile(), false);
final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(this);
final int interfaceArity = interfaceMethod.getParameterList().getParametersCount();
for (ResolveResult resolveResult : result) {
final PsiElement element = resolveResult.getElement();
if (element instanceof PsiMethod) {
final boolean isStatic = ((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC);
if (qualifierResolveResult.isReferenceTypeQualified() && getReferenceNameElement() instanceof PsiIdentifier) {
final int parametersCount = ((PsiMethod)element).getParameterList().getParametersCount();
if (parametersCount == interfaceArity && isStatic) {
return true;
}
if (parametersCount == interfaceArity - 1 && !isStatic) {
return true;
}
if (((PsiMethod)element).isVarArgs()) return true;
} else if (!isStatic) {
return true;
}
} else if (element instanceof PsiClass) {
return true;
}
}
return false;
}
@Override
public PsiMember getPotentiallyApplicableMember() {
return CachedValuesManager.getCachedValue(this, new CachedValueProvider<PsiMember>() {
@Nullable
@Override
public Result<PsiMember> compute() {
return Result.createSingleDependency(getPotentiallyApplicableMemberInternal(),
PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
});
}
private PsiMember getPotentiallyApplicableMemberInternal() {
final PsiElement element = getReferenceNameElement();
final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(this);
final PsiClass containingClass = qualifierResolveResult.getContainingClass();
if (containingClass != null) {
PsiMethod[] methods = null;
if (element instanceof PsiIdentifier) {
methods = containingClass.findMethodsByName(element.getText(), !qualifierResolveResult.isReferenceTypeQualified());
}
else if (isConstructor()) {
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(getProject());
final PsiClass arrayClass = factory.getArrayClass(PsiUtil.getLanguageLevel(this));
if (arrayClass == containingClass) {
final PsiType componentType = qualifierResolveResult.getSubstitutor().substitute(arrayClass.getTypeParameters()[0]);
LOG.assertTrue(componentType != null, qualifierResolveResult.getSubstitutor());
methods = new PsiMethod[] {factory.createMethodFromText("public " + componentType.createArrayType().getCanonicalText() + " __array__(int i) {return null;}", this)};
} else {
methods = containingClass.getConstructors();
}
}
if (methods != null) {
PsiMethod psiMethod = null;
if (methods.length > 0) {
for (PsiMethod method : methods) {
if (PsiUtil.isAccessible(method, this, null)) {
if (psiMethod != null) return null;
psiMethod = method;
}
}
if (psiMethod == null) return null;
if (psiMethod.isVarArgs()) return null;
if (psiMethod.getTypeParameters().length > 0) {
final PsiReferenceParameterList parameterList = getParameterList();
return parameterList != null && parameterList.getTypeParameterElements().length > 0 ? psiMethod : null;
}
}
if (containingClass.isPhysical() && containingClass.hasTypeParameters()) {
final PsiElement qualifier = getQualifier();
if (qualifier instanceof PsiTypeElement) {
final PsiJavaCodeReferenceElement referenceElement = ((PsiTypeElement)qualifier).getInnermostComponentReferenceElement();
if (referenceElement != null) {
final PsiReferenceParameterList parameterList = referenceElement.getParameterList();
if (parameterList == null || parameterList.getTypeParameterElements().length == 0) {
return null;
}
}
} else if (qualifier instanceof PsiReferenceExpression) {
final PsiReferenceExpression expression = (PsiReferenceExpression)qualifier;
if (qualifierResolveResult.isReferenceTypeQualified()) {
final PsiReferenceParameterList parameterList = expression.getParameterList();
if (parameterList == null || parameterList.getTypeParameterElements().length == 0) {
return null;
}
}
}
}
return psiMethod == null ? containingClass : psiMethod;
}
}
return null;
}
@Override
public PsiExpression getQualifierExpression() {
final PsiElement qualifier = getQualifier();
return qualifier instanceof PsiExpression ? (PsiExpression)qualifier : null;
}
@Override
public PsiType getType() {
return new PsiMethodReferenceType(this);
}
@Override
public PsiElement getReferenceNameElement() {
final PsiElement element = getLastChild();
return element instanceof PsiIdentifier || PsiUtil.isJavaToken(element, JavaTokenType.NEW_KEYWORD) ? element : null;
}
@Override
public void processVariants(@NotNull final PsiScopeProcessor processor) {
final FilterScopeProcessor proc = new FilterScopeProcessor(ElementClassFilter.METHOD, processor);
PsiScopesUtil.resolveAndWalk(proc, this, null, true);
}
@Override
public void setQualifierExpression(@Nullable PsiExpression newQualifier) throws IncorrectOperationException {
if (newQualifier == null) {
super.setQualifierExpression(newQualifier);
return;
}
final PsiExpression expression = getQualifierExpression();
if (expression != null) {
expression.replace(newQualifier);
} else {
final PsiElement qualifier = getQualifier();
if (qualifier != null) {
qualifier.replace(newQualifier);
}
}
}
@Override
public int getChildRole(ASTNode child) {
final IElementType elType = child.getElementType();
if (elType == JavaTokenType.DOUBLE_COLON) {
return ChildRole.DOUBLE_COLON;
} else if (elType == JavaTokenType.IDENTIFIER) {
return ChildRole.REFERENCE_NAME;
} else if (elType == JavaElementType.REFERENCE_EXPRESSION) {
return ChildRole.CLASS_REFERENCE;
}
return ChildRole.EXPRESSION;
}
@NotNull
@Override
public JavaResolveResult[] multiResolve(final boolean incompleteCode) {
FileElement fileElement = SharedImplUtil.findFileElement(this);
if (fileElement == null) {
LOG.error("fileElement == null!");
return JavaResolveResult.EMPTY_ARRAY;
}
final PsiManagerEx manager = fileElement.getManager();
if (manager == null) {
LOG.error("getManager() == null!");
return JavaResolveResult.EMPTY_ARRAY;
}
PsiFile file = SharedImplUtil.getContainingFile(fileElement);
boolean valid = file != null && file.isValid();
if (!valid) {
LOG.error("invalid!");
return JavaResolveResult.EMPTY_ARRAY;
}
final Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.ourRefs.get();
if (map != null && map.containsKey(this)) {
return RESOLVER.resolve(this, file, incompleteCode);
}
ResolveResult[] results = ResolveCache.getInstance(getProject()).resolveWithCaching(this, RESOLVER, true, incompleteCode, file);
return results.length == 0 ? JavaResolveResult.EMPTY_ARRAY : (JavaResolveResult[])results;
}
@Override
public PsiElement getQualifier() {
final PsiElement element = getFirstChild();
return element instanceof PsiExpression || element instanceof PsiTypeElement ? element : null;
}
@Override
public TextRange getRangeInElement() {
final PsiElement element = getReferenceNameElement();
if (element != null) {
final int offsetInParent = element.getStartOffsetInParent();
return new TextRange(offsetInParent, offsetInParent + element.getTextLength());
}
final PsiElement colons = findPsiChildByType(JavaTokenType.DOUBLE_COLON);
if (colons != null) {
final int offsetInParent = colons.getStartOffsetInParent();
return new TextRange(offsetInParent, offsetInParent + colons.getTextLength());
}
LOG.error(getText());
return null;
}
@NotNull
@Override
public String getCanonicalText() {
return getText();
}
@Override
public boolean isReferenceTo(final PsiElement element) {
if (!(element instanceof PsiMethod)) return false;
final PsiMethod method = (PsiMethod)element;
final PsiElement nameElement = getReferenceNameElement();
if (nameElement instanceof PsiIdentifier) {
if (!nameElement.getText().equals(method.getName())) return false;
}
else if (PsiUtil.isJavaToken(nameElement, JavaTokenType.NEW_KEYWORD)) {
if (!method.isConstructor()) return false;
}
return element.getManager().areElementsEquivalent(element, resolve());
}
@Override
public void accept(@NotNull final PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitMethodReferenceExpression(this);
}
else {
visitor.visitElement(this);
}
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return this;
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
PsiElement oldIdentifier = findChildByRoleAsPsiElement(ChildRole.REFERENCE_NAME);
if (oldIdentifier == null) {
oldIdentifier = findChildByRoleAsPsiElement(ChildRole.CLASS_REFERENCE);
}
if (oldIdentifier == null) {
throw new IncorrectOperationException();
}
final String oldRefName = oldIdentifier.getText();
if (PsiKeyword.THIS.equals(oldRefName) ||
PsiKeyword.SUPER.equals(oldRefName) ||
PsiKeyword.NEW.equals(oldRefName) ||
Comparing.strEqual(oldRefName, newElementName)) {
return this;
}
PsiIdentifier identifier = JavaPsiFacade.getInstance(getProject()).getElementFactory().createIdentifier(newElementName);
oldIdentifier.replace(identifier);
return this;
}
@Override
public boolean isConstructor() {
final PsiElement element = getReferenceNameElement();
return element instanceof PsiKeyword && PsiKeyword.NEW.equals(element.getText());
}
@Override
public String toString() {
return "PsiMethodReferenceExpression:" + getText();
}
@Override
public boolean isAcceptable(PsiType left) {
if (left instanceof PsiIntersectionType) {
for (PsiType conjunct : ((PsiIntersectionType)left).getConjuncts()) {
if (isAcceptable(conjunct)) return true;
}
return false;
}
final PsiElement argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
final boolean isExact = isExact();
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList) && isExact) {
final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
if (candidateProperties != null && !InferenceSession.isPertinentToApplicability(this, candidateProperties.getMethod())) {
return true;
}
}
left = FunctionalInterfaceParameterizationUtil.getGroundTargetType(left);
if (!isPotentiallyCompatible(left)) {
return false;
}
if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
if (!isExact) {
return true;
}
}
// A method reference is congruent with a function type if the following are true:
// The function type identifies a single compile-time declaration corresponding to the reference.
// One of the following is true:
// i)The return type of the function type is void.
// ii)The return type of the function type is R;
// the result of applying capture conversion (5.1.10) to the return type of the invocation type (15.12.2.6) of the chosen declaration is R',
// where R is the target type that may be used to infer R'; neither R nor R' is void; and R' is compatible with R in an assignment context.
Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
final JavaResolveResult result;
try {
if (map.put(this, left) != null) {
return false;
}
result = advancedResolve(false);
}
finally {
map.remove(this);
}
final PsiElement resolve = result.getElement();
if (resolve == null) {
return false;
}
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(left);
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
if (interfaceMethod != null) {
final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(left);
if (interfaceReturnType == PsiType.VOID || interfaceReturnType == null) {
return true;
}
final PsiSubstitutor subst = result.getSubstitutor();
PsiType methodReturnType = null;
PsiClass containingClass = null;
if (resolve instanceof PsiMethod) {
containingClass = ((PsiMethod)resolve).getContainingClass();
PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(this, this, (PsiMethod)resolve, null, PsiUtil.getLanguageLevel(this));
if (returnType == null) {
returnType = ((PsiMethod)resolve).getReturnType();
}
if (returnType == PsiType.VOID) {
return false;
}
methodReturnType = subst.substitute(returnType);
}
else if (resolve instanceof PsiClass) {
if (resolve == JavaPsiFacade.getElementFactory(resolve.getProject()).getArrayClass(PsiUtil.getLanguageLevel(resolve))) {
final PsiTypeParameter[] typeParameters = ((PsiClass)resolve).getTypeParameters();
if (typeParameters.length == 1) {
final PsiType arrayComponentType = subst.substitute(typeParameters[0]);
if (arrayComponentType == null) {
return false;
}
methodReturnType = arrayComponentType.createArrayType();
}
}
containingClass = (PsiClass)resolve;
}
if (methodReturnType == null) {
if (containingClass == null) {
return false;
}
methodReturnType = JavaPsiFacade.getElementFactory(getProject()).createType(containingClass, subst);
}
return TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType, false);
}
return false;
}
@Nullable
@Override
public Icon getIcon(int flags) {
return AllIcons.Nodes.AnonymousClass;
}
}