/*
* Copyright 2000-2009 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.infos;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JavaVersionService;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy;
import com.intellij.psi.impl.source.resolve.ParameterTypeInferencePolicy;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ConcurrentWeakHashMap;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
/**
* @author ik, dsl
*/
public class MethodCandidateInfo extends CandidateInfo{
public static final ThreadLocal<Map<PsiElement, Pair<PsiMethod, PsiSubstitutor>>> CURRENT_CANDIDATE = new ThreadLocal<Map<PsiElement, Pair<PsiMethod, PsiSubstitutor>>>();
@ApplicabilityLevelConstant private int myApplicabilityLevel = 0;
private final PsiElement myArgumentList;
private final PsiType[] myArgumentTypes;
private final PsiType[] myTypeArguments;
private PsiSubstitutor myCalcedSubstitutor = null;
private final LanguageLevel myLanguageLevel;
public MethodCandidateInfo(PsiElement candidate,
PsiSubstitutor substitutor,
boolean accessProblem,
boolean staticsProblem,
PsiElement argumentList,
PsiElement currFileContext,
@Nullable PsiType[] argumentTypes,
PsiType[] typeArguments) {
this(candidate, substitutor, accessProblem, staticsProblem, argumentList, currFileContext, argumentTypes, typeArguments,
PsiUtil.getLanguageLevel(argumentList));
}
public MethodCandidateInfo(PsiElement candidate,
PsiSubstitutor substitutor,
boolean accessProblem,
boolean staticsProblem,
PsiElement argumentList,
PsiElement currFileContext,
@Nullable PsiType[] argumentTypes,
PsiType[] typeArguments,
@NotNull LanguageLevel languageLevel) {
super(candidate, substitutor, accessProblem, staticsProblem, currFileContext);
myArgumentList = argumentList;
myArgumentTypes = argumentTypes;
myTypeArguments = typeArguments;
myLanguageLevel = languageLevel;
}
public boolean isApplicable(){
return getApplicabilityLevel() != ApplicabilityLevel.NOT_APPLICABLE;
}
@ApplicabilityLevelConstant
private int getApplicabilityLevelInner() {
if (myArgumentTypes == null) return ApplicabilityLevel.NOT_APPLICABLE;
int level = PsiUtil.getApplicabilityLevel(getElement(), getSubstitutor(), myArgumentTypes, myLanguageLevel);
if (level > ApplicabilityLevel.NOT_APPLICABLE && !isTypeArgumentsApplicable()) level = ApplicabilityLevel.NOT_APPLICABLE;
return level;
}
@ApplicabilityLevelConstant
public int getApplicabilityLevel() {
if(myApplicabilityLevel == 0){
myApplicabilityLevel = getApplicabilityLevelInner();
}
return myApplicabilityLevel;
}
public PsiSubstitutor getSiteSubstitutor() {
return super.getSubstitutor();
}
@Override
public PsiSubstitutor getSubstitutor() {
return getSubstitutor(true);
}
public PsiSubstitutor getSubstitutor(boolean includeReturnConstraint) {
if (myCalcedSubstitutor == null || !includeReturnConstraint) {
PsiSubstitutor incompleteSubstitutor = super.getSubstitutor();
PsiMethod method = getElement();
if (myTypeArguments == null) {
final RecursionGuard.StackStamp stackStamp = PsiDiamondType.ourDiamondGuard.markStack();
final PsiSubstitutor inferredSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE, includeReturnConstraint);
if (!stackStamp.mayCacheNow() || !includeReturnConstraint) {
return inferredSubstitutor;
}
myCalcedSubstitutor = inferredSubstitutor;
}
else {
PsiTypeParameter[] typeParams = method.getTypeParameters();
for (int i = 0; i < myTypeArguments.length && i < typeParams.length; i++) {
incompleteSubstitutor = incompleteSubstitutor.put(typeParams[i], myTypeArguments[i]);
}
myCalcedSubstitutor = incompleteSubstitutor;
}
}
return myCalcedSubstitutor;
}
public boolean isTypeArgumentsApplicable() {
final PsiMethod psiMethod = getElement();
PsiTypeParameter[] typeParams = psiMethod.getTypeParameters();
if (myTypeArguments != null && typeParams.length != myTypeArguments.length && !PsiUtil.isLanguageLevel7OrHigher(psiMethod)){
return typeParams.length == 0 && JavaVersionService.getInstance().isAtLeast(psiMethod, JavaSdkVersion.JDK_1_7);
}
PsiSubstitutor substitutor = getSubstitutor();
return GenericsUtil.isTypeArgumentsApplicable(typeParams, substitutor, getParent());
}
protected PsiElement getParent() {
return myArgumentList != null ? myArgumentList.getParent() : myArgumentList;
}
@Override
public boolean isValidResult(){
return super.isValidResult() && isApplicable();
}
@Override
public PsiMethod getElement(){
return (PsiMethod)super.getElement();
}
public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy, boolean includeReturnConstraint) {
return inferTypeArguments(policy, myArgumentList instanceof PsiExpressionList
? ((PsiExpressionList)myArgumentList).getExpressions()
: PsiExpression.EMPTY_ARRAY, includeReturnConstraint);
}
public PsiSubstitutor inferSubstitutorFromArgs(@NotNull ParameterTypeInferencePolicy policy, final PsiExpression[] arguments) {
if (myTypeArguments == null) {
return inferTypeArguments(policy, arguments, true);
}
else {
PsiSubstitutor incompleteSubstitutor = super.getSubstitutor();
PsiMethod method = getElement();
if (method != null) {
PsiTypeParameter[] typeParams = method.getTypeParameters();
for (int i = 0; i < myTypeArguments.length && i < typeParams.length; i++) {
incompleteSubstitutor = incompleteSubstitutor.put(typeParams[i], myTypeArguments[i]);
}
}
return incompleteSubstitutor;
}
}
public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy,
@NotNull PsiExpression[] arguments,
boolean includeReturnConstraint) {
Map<PsiElement, Pair<PsiMethod, PsiSubstitutor>> map = CURRENT_CANDIDATE.get();
if (map == null) {
map = new ConcurrentWeakHashMap<PsiElement, Pair<PsiMethod, PsiSubstitutor>>();
CURRENT_CANDIDATE.set(map);
}
final PsiMethod method = getElement();
final Pair<PsiMethod, PsiSubstitutor> alreadyThere = includeReturnConstraint
? map.put(getMarkerList(), Pair.create(method, super.getSubstitutor()))
: null;
try {
PsiTypeParameter[] typeParameters = method.getTypeParameters();
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null && PsiUtil.isRawSubstitutor(containingClass, mySubstitutor)) {
Project project = containingClass.getProject();
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
return javaPsiFacade.getElementFactory().createRawSubstitutor(mySubstitutor, typeParameters);
}
}
final PsiElement parent = getParent();
if (parent == null) return PsiSubstitutor.EMPTY;
Project project = method.getProject();
JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
return javaPsiFacade.getResolveHelper()
.inferTypeArguments(typeParameters, method.getParameterList().getParameters(), arguments, mySubstitutor, parent, policy, myLanguageLevel);
}
finally {
if (alreadyThere == null) map.remove(getMarkerList());
}
}
protected PsiElement getMarkerList() {
return myArgumentList;
}
public boolean isInferencePossible() {
return myArgumentList != null && myArgumentList.isValid();
}
public static Pair<PsiMethod, PsiSubstitutor> getCurrentMethod(PsiElement context) {
final Map<PsiElement,Pair<PsiMethod,PsiSubstitutor>> currentMethodCandidates = CURRENT_CANDIDATE.get();
return currentMethodCandidates != null ? currentMethodCandidates.get(context) : null;
}
public static class ApplicabilityLevel {
public static final int NOT_APPLICABLE = 1;
public static final int VARARGS = 2;
public static final int FIXED_ARITY = 3;
}
@MagicConstant(intValues = {ApplicabilityLevel.NOT_APPLICABLE, ApplicabilityLevel.VARARGS, ApplicabilityLevel.FIXED_ARITY})
public @interface ApplicabilityLevelConstant {
}
}