/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.lang.psi.impl.resolvers;
import com.google.common.collect.Maps;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiEllipsisType;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.PsiWildcardType;
import gw.internal.gosu.parser.GosuMethodInfo;
import gw.internal.gosu.parser.IReducedDelegateFunctionSymbol;
import gw.internal.gosu.parser.ReducedDynamicFunctionSymbol;
import gw.internal.gosu.parser.TypeLord;
import gw.lang.parser.IReducedDynamicFunctionSymbol;
import gw.lang.reflect.IAttributedFeatureInfo;
import gw.lang.reflect.IBlockType;
import gw.lang.reflect.ICompoundType;
import gw.lang.reflect.IConstructorInfo;
import gw.lang.reflect.IErrorType;
import gw.lang.reflect.IHasParameterInfos;
import gw.lang.reflect.IMetaType;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IShadowingType;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeVariableType;
import gw.lang.reflect.ModifiedParameterInfo;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuMethodInfo;
import gw.lang.reflect.java.IJavaClassInfo;
import gw.lang.reflect.java.IJavaMethodInfo;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.java.JavaTypes;
import gw.plugin.ij.lang.psi.api.AbstractFeatureResolver;
import gw.plugin.ij.lang.psi.api.IGosuResolveResult;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuTypeDefinition;
import gw.plugin.ij.lang.psi.custom.CustomGosuClass;
import gw.plugin.ij.lang.psi.impl.CustomPsiClassCache;
import gw.plugin.ij.lang.psi.impl.GosuPsiSubstitutor;
import gw.plugin.ij.lang.psi.impl.GosuResolveResultImpl;
import gw.plugin.ij.util.JavaPsiFacadeUtil;
import gw.plugin.ij.util.TypeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class GosuMethodResolver extends AbstractFeatureResolver {
@Nullable
public IGosuResolveResult resolveMethodOrConstructor(@NotNull IHasParameterInfos info, @NotNull PsiElement context) {
if (info instanceof IGosuMethodInfo) {
final IReducedDynamicFunctionSymbol dfs = ((IGosuMethodInfo) info).getDfs();
if (dfs instanceof IReducedDelegateFunctionSymbol) {
info = ((IReducedDelegateFunctionSymbol) dfs).getTargetMethodInfo();
}
}
IType ownersType = findRealOwnersType(info);
if (ownersType == null || TypeSystem.isDeleted(ownersType)) {
return null;
}
IType concreteOwnersType = TypeUtil.getConcreteType(ownersType);
if (concreteOwnersType == null) {
return null;
}
if (concreteOwnersType instanceof ICompoundType) {
for (IType type : ((ICompoundType) concreteOwnersType).getTypes()) {
final IGosuResolveResult result = resolveGosuOrJavaMethod(info, context, type, TypeUtil.getConcreteType(type));
if (result != null) {
return result;
}
}
return null;
}
return resolveGosuOrJavaMethod(info, context, ownersType, concreteOwnersType);
}
private IGosuResolveResult resolveAsCustomClass(IHasParameterInfos info) {
IType type = info.getOwnersType();
if (info instanceof IMethodInfo &&
!(type instanceof IGosuClass || type instanceof IJavaType || type instanceof IBlockType || type instanceof IErrorType)) {
CustomGosuClass psiClass = CustomPsiClassCache.instance().getPsiClass(type);
if (psiClass != null) {
PsiElement psiMethod = psiClass.getMethod((IMethodInfo) info, null);
if (psiMethod != null) {
return new GosuResolveResultImpl(psiMethod, true, info);
}
}
}
return null;
}
private IType findRealOwnersType(@NotNull IHasParameterInfos info) {
if (info instanceof IJavaMethodInfo) {
final IJavaClassInfo enclosingClass = ((IJavaMethodInfo) info).getMethod().getEnclosingClass();
final IType type = enclosingClass.getJavaType();
if (type instanceof IShadowingType) {
for (IType shadowedType : ((IShadowingType) type).getShadowedTypes()) {
if (shadowedType.getName().equals(enclosingClass.getName())) {
return shadowedType;
}
}
}
IType ownersType = info.getOwnersType();
if (ownersType instanceof IJavaType) {
return ownersType;
}
return type;
} else {
return info.getOwnersType();
}
}
//private
@Nullable
public static IGosuResolveResult findMethodOrConstructor(@NotNull PsiClass targetClass, @NotNull IHasParameterInfos targetMethodInfo, @NotNull Map<String, IType> typeVarMap) {
String targetMethodName = normalize(targetMethodInfo.getDisplayName());
IParameterInfo[] targetParameters = targetMethodInfo.getParameters();
IType[] paramTypes = new IType[targetParameters.length];
for (int i = 0; i < paramTypes.length; i++) {
paramTypes[i] = targetParameters[i] instanceof ModifiedParameterInfo ?
((ModifiedParameterInfo) targetParameters[i]).getOriginalType() : targetParameters[i].getFeatureType();
}
PsiMethod result = null;
if (maybeCheckSubstitutors(targetClass)) {
List<Pair<PsiMethod, PsiSubstitutor>> methodsAndSubstitutors = targetClass.findMethodsAndTheirSubstitutorsByName(targetMethodName, true);
for (Pair<PsiMethod, PsiSubstitutor> pair : methodsAndSubstitutors) {
PsiMethod method = pair.getFirst();
if (matchMethod(method, targetMethodInfo, targetMethodName, paramTypes, typeVarMap, pair.getSecond(), targetMethodInfo instanceof IConstructorInfo)) {
result = method;
break;
}
}
}
if (result == null) {
result = findMethodOrConstructorDeep(targetClass, targetMethodInfo, targetMethodName, paramTypes, typeVarMap, new HashSet<PsiClass>());
}
if (result == null) {
return null;
} else {
GosuPsiSubstitutor substitutor = new GosuPsiSubstitutor(typeVarMap, targetClass);
return new GosuResolveResultImpl(result, null, substitutor, true, true, targetMethodInfo);
}
}
@Nullable
private static IGosuResolveResult resolveGosuOrJavaMethod(@NotNull IHasParameterInfos info, PsiElement ctx, @NotNull IType ownersType, @NotNull IType concreteOwnersType) {
final PsiClass psiClass;
if (IGosuClass.ProxyUtil.isProxyClass(concreteOwnersType.getName())) {
psiClass = (PsiClass) PsiTypeResolver.getProxiedPsiClass(concreteOwnersType, ctx);
} else {
final PsiElement element = PsiTypeResolver.resolveType(concreteOwnersType, ctx);
psiClass = element instanceof PsiClass ? (PsiClass) element : null;
}
if (psiClass == null) {
return null;
}
if (info instanceof IConstructorInfo) {
if (((IConstructorInfo) info).isDefault() ||
(!(psiClass instanceof IGosuTypeDefinition) && psiClass.isAnnotationType())) {
return new GosuResolveResultImpl(psiClass, true, info);
}
}
final Map<String, IType> typeVarMap = ownersType.isParameterizedType() ?
createTypeVarMap(ownersType.getTypeParameters(), psiClass.getTypeParameters()) :
Collections.<String, IType>emptyMap();
Set<IHasParameterInfos> visited = new HashSet<>();
while( info instanceof GosuMethodInfo ) {
ReducedDynamicFunctionSymbol dfs = ((GosuMethodInfo) info).getDfs();
IReducedDynamicFunctionSymbol backingDfs = dfs.getBackingDfs();
if( backingDfs != null && backingDfs != dfs ) {
IAttributedFeatureInfo backingInfo = backingDfs.getMethodOrConstructorInfo();
if( !(backingInfo instanceof IHasParameterInfos) || backingInfo == info ) {
break;
}
if( visited.contains( info ) ) {
throw new RuntimeException( "Dfs cycle detected: " + dfs.getType().getEnclosingType() + " : " + dfs.getDisplayName() );
}
visited.add( info );
info = (IHasParameterInfos) backingInfo;
}
else {
break;
}
}
return findMethodOrConstructor(psiClass, info, typeVarMap);
}
@Nullable
private static PsiMethod findMethodOrConstructorDeep(
@Nullable PsiClass targetClass, @NotNull IHasParameterInfos targetMethodInfo, @NotNull String targetMethodName, @NotNull IType[] targetParamTypes, @NotNull Map<String, IType> typeVarMap, @NotNull Set<PsiClass> visited) {
ProgressManager.checkCanceled();
if (targetClass == null || visited.contains(targetClass)) {
return null;
}
visited.add(targetClass);
PsiMethod result = null;
for (PsiMethod method : getMethodsOrContructors(targetMethodInfo, targetClass)) {
if (matchMethod(method, targetMethodInfo, targetMethodName, targetParamTypes, typeVarMap, null, targetMethodInfo instanceof IConstructorInfo)) {
result = method;
break;
}
}
if (result == null) {
result = findMethodOrConstructorDeep(targetClass.getSuperClass(), targetMethodInfo, targetMethodName, targetParamTypes, typeVarMap, visited);
}
return result;
}
private static boolean maybeCheckSubstitutors(@NotNull PsiClass targetClass) {
PsiTypeParameter[] typeParameters = targetClass.getTypeParameters();
if (typeParameters.length > 0) {
return true;
}
PsiClass superClass = targetClass.getSuperClass();
if (superClass != null) {
// XXX Hack to avoid IJ throwing an illegal state exception when asked for substitutors for an enum
if ("java.lang.Enum".equals(superClass.getQualifiedName())) {
return false;
}
typeParameters = superClass.getTypeParameters();
return typeParameters.length > 0;
}
return false;
}
private static boolean matchMethod(@NotNull PsiMethod method, @NotNull IHasParameterInfos targetMethodInfo, @NotNull String targetMethodName, @NotNull IType[] paramTypes, @NotNull Map<String, IType> typeVarMap, PsiSubstitutor substitutor, boolean isConstructor) {
if (matchName(method, targetMethodInfo, targetMethodName, isConstructor)) {
return false;
}
boolean isStatic = targetMethodInfo.isStatic();
if (!isConstructor && isStatic != method.getModifierList().hasModifierProperty(PsiModifier.STATIC)) {
return false;
}
PsiParameterList parameterList = method.getParameterList();
if (paramTypes.length != parameterList.getParametersCount()) {
return false;
}
PsiParameter[] psiParams = parameterList.getParameters();
for (int i = 0; i < paramTypes.length; i++) {
if (!matchParameter(paramTypes[i], psiParams[i].getType(), typeVarMap, substitutor, isStatic)) {
return false;
}
}
return true;
}
// TODO: use GosuProperties class
private static boolean matchName(@NotNull PsiMethod method, @NotNull IHasParameterInfos targetMethodInfo, @NotNull String targetMethodName, boolean isConstructor) {
if (targetMethodName.startsWith("@")) {
IType returnType = ((IGosuMethodInfo) targetMethodInfo).getReturnType();
if ((returnType.equals(JavaTypes.BOOLEAN()) || returnType.equals(JavaTypes.pBOOLEAN()) &&
!method.getName().equals("is" + targetMethodName.substring(1)))) {
return true;
}
if (!method.getName().equals("get" + targetMethodName.substring(1)) && !method.getName().equals("set" + targetMethodName.substring(1))) {
return true;
}
} else if (!targetMethodName.equals(method.getName()) && !(isConstructor && "construct".equals(method.getName()))) {
return true;
}
return false;
}
private static boolean matchParameter(IType type, @NotNull PsiType candidate, @NotNull Map<String, IType> typeVarMap, @Nullable PsiSubstitutor substitutor, boolean isStatic) {
// XXX maybe use PsiTypeVisitor & candidate.accept() instead of instanceof here? See TypeParameterSearcher
boolean result = false;
if (type instanceof IMetaType) {
type = ((IMetaType) type).getType();
}
IType basicPattern = type.isParameterizedType() ? type.getGenericType() : type;
String patternName = basicPattern.getName();
IType candidateType;
if( candidate instanceof PsiEllipsisType ) {
candidate = ((PsiEllipsisType)candidate).toArrayType();
}
if( candidate instanceof PsiArrayType && type.isArray() ) {
return matchParameter( type.getComponentType(), ((PsiArrayType)candidate).getComponentType(), typeVarMap, substitutor, isStatic );
}
if (candidate instanceof PsiClassType) {
PsiClassType candidateAsPsiClass = (PsiClassType) candidate;
PsiClass resolvedCandidate = candidateAsPsiClass.resolve();
String candidateName;
if (resolvedCandidate != null) {
if (resolvedCandidate instanceof PsiTypeParameter && substitutor != null) {
resolvedCandidate = maybeSubstituteType(resolvedCandidate, substitutor);
if (isStatic) {
resolvedCandidate = stripToBoundingType((PsiTypeParameter) resolvedCandidate);
}
}
if (resolvedCandidate instanceof PsiTypeParameter) {
candidateName = resolvedCandidate.getName();
} else {
candidateName = resolvedCandidate.getQualifiedName();
}
} else {
candidateName = candidate.getCanonicalText();
}
candidateType = typeVarMap.get(candidateName);
if (candidateType != null) {
result = type.equals(candidateType);
}
if( !result ) {
result = candidateName.equals(patternName);
if (result) {
if( type instanceof ITypeVariableType && resolvedCandidate instanceof PsiTypeParameter ) {
PsiClassType boundingType = JavaPsiFacadeUtil.getElementFactory( resolvedCandidate.getProject() ).createType( stripToBoundingType( (PsiTypeParameter)resolvedCandidate ) );
result = matchParameter( TypeLord.getPureGenericType( ((ITypeVariableType)type).getBoundingType() ), boundingType, typeVarMap, substitutor, isStatic ) ||
matchParameter( ((ITypeVariableType)type).getBoundingType(), boundingType, typeVarMap, substitutor, isStatic );
}
else {
PsiType[] candidateTypeParams = candidateAsPsiClass.getParameters();
IType[] patternTypeParams = type.getTypeParameters();
int candidateTypeParamLength = candidateTypeParams != null ? candidateTypeParams.length : 0;
int patternTypeParamLength = patternTypeParams != null ? patternTypeParams.length : 0;
if (patternTypeParamLength == candidateTypeParamLength) {
for (int i = 0; i < patternTypeParamLength; i++) {
if (!matchParameter(patternTypeParams[i], candidateTypeParams[i], typeVarMap, substitutor, isStatic)) {
result = false;
break;
}
}
} else {
result = false;
}
}
}
}
} else {
PsiType unboundedCandidate = removeBounds(candidate);
candidateType = typeVarMap.get(unboundedCandidate.getCanonicalText());
if (candidateType != null) {
result = type.equals(candidateType);
} else {
result = unboundedCandidate.equalsToText(patternName);
}
}
if (!result && type instanceof IShadowingType) {
return matchShadowedTypes((IShadowingType) type, candidate, typeVarMap, substitutor, isStatic);
}
return result;
}
private static boolean matchShadowedTypes(@NotNull IShadowingType type, @NotNull PsiType candidate, @NotNull Map<String, IType> typeVarMap, PsiSubstitutor substitutor, boolean isStatic) {
for (IType shadowedType : type.getShadowedTypes()) {
if (matchParameter(shadowedType, candidate, typeVarMap, substitutor, isStatic)) {
return true;
}
}
return false;
}
// Handles issue with getDisplayName() for java generic constructors, e.g. HashMap<String, String>
@NotNull
private static String normalize(@NotNull String displayName) {
int angleIndex = displayName.indexOf('<');
return angleIndex != -1 ? displayName.substring(0, angleIndex) : displayName;
}
@Nullable
private static PsiType removeBounds(@NotNull PsiType type) {
if( type instanceof PsiWildcardType ) {
// Always use *Extends* bound because Gosu treats all wildcard types covariantly
// i.e., the bound s/b Object for contravariant wildcards
return ((PsiWildcardType)type).getExtendsBound();
} else {
return type;
}
}
@Nullable
private static PsiClass maybeSubstituteType(PsiClass resolvedCandidate, @NotNull PsiSubstitutor substitutor) {
PsiType substitutedType = substitutor.getSubstitutionMap().get(resolvedCandidate);
if (substitutedType != null && substitutedType instanceof PsiClassType) {
PsiClass resolvedSubstitution = ((PsiClassType) substitutedType).resolve();
if (resolvedSubstitution != null) {
return resolvedSubstitution;
}
}
return resolvedCandidate;
}
@NotNull
private static PsiMethod[] getMethodsOrContructors(IHasParameterInfos targetMethodInfo, @NotNull PsiClass targetClass) {
if (targetMethodInfo instanceof IMethodInfo) {
return targetClass.getMethods();
} else {
return targetClass.getConstructors();
}
}
@NotNull
private static Map<String, IType> createTypeVarMap(@Nullable IType[] paramTypes, @Nullable PsiTypeParameter[] typeVars) {
Map<String, IType> typeVarMap = Maps.newHashMap();
if (typeVars != null && paramTypes != null) {
int len = Math.min(typeVars.length, paramTypes.length);
for (int i = 0; i < len; ++i) {
typeVarMap.put(typeVars[i].getName(), paramTypes[i]);
}
}
return typeVarMap;
}
@Nullable
private static PsiClass stripToBoundingType(@NotNull PsiTypeParameter typeParam) {
PsiClass result = null;
PsiReferenceList extendsList = typeParam.getExtendsList();
PsiClassType[] referencedTypes = extendsList.getReferencedTypes();
if (referencedTypes.length > 0) {
// todo deal with entire list as composite type (e.g. List & Map, etc.)
result = referencedTypes[0].resolve();
}
if (result == null) {
result = PsiTypeResolver.resolveType("java.lang.Object", typeParam);
}
return result != null ? result : typeParam;
}
@NotNull
private static IHasParameterInfos resolveParamerizedFeatureInfo(@NotNull IHasParameterInfos featureInfo) {
if (featureInfo.getOwnersType().isParameterizedType()) {
if (featureInfo instanceof IGosuMethodInfo) {
IReducedDynamicFunctionSymbol dfs = ((IGosuMethodInfo) featureInfo).getDfs();
if (dfs != null) {
IReducedDynamicFunctionSymbol backingDfs = dfs.getBackingDfs();
if (backingDfs != null) {
IAttributedFeatureInfo resolvedFeatureInfo = backingDfs.getMethodOrConstructorInfo();
if (resolvedFeatureInfo instanceof IHasParameterInfos) {
featureInfo = (IHasParameterInfos) resolvedFeatureInfo;
}
}
}
}
}
return featureInfo;
}
}