/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.lang.psi.impl.resolvers;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import gw.lang.parser.IBlockClass;
import gw.lang.parser.ICapturedSymbol;
import gw.lang.parser.IDynamicPropertySymbol;
import gw.lang.parser.IDynamicSymbol;
import gw.lang.parser.IInitializerSymbol;
import gw.lang.parser.IReducedSymbol;
import gw.lang.parser.ITypedSymbol;
import gw.lang.parser.Keyword;
import gw.lang.parser.SymbolType;
import gw.lang.parser.expressions.IMethodCallExpression;
import gw.lang.parser.expressions.INewExpression;
import gw.lang.reflect.*;
import gw.lang.reflect.gs.ICompilableType;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.ITemplateType;
import gw.plugin.ij.lang.parser.GosuElementTypes;
import gw.plugin.ij.lang.psi.api.AbstractFeatureResolver;
import gw.plugin.ij.lang.psi.api.statements.IGosuField;
import gw.plugin.ij.lang.psi.api.statements.params.IGosuParameter;
import gw.plugin.ij.lang.psi.impl.GosuBaseElementImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuDirectiveExpressionImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuMethodCallExpressionImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuNewExpressionImpl;
import gw.plugin.ij.lang.psi.impl.expressions.GosuReferenceExpressionImpl;
import gw.plugin.ij.lang.psi.impl.statements.GosuVariableImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuClassDefinitionImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.members.GosuMethodImpl;
import gw.plugin.ij.util.InjectedElementEditor;
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.Arrays;
import static com.google.common.collect.Iterables.filter;
public class GosuFeatureResolver extends AbstractFeatureResolver {
@Nullable
@Override
public PsiElement resolve(@NotNull IPropertyInfo propertyInfo, @NotNull PsiElement context) {
IType ownersType = propertyInfo.getOwnersType();
PsiElement psiElement = resolveProperty(ownersType, propertyInfo.getName(), context);
if (psiElement != null) {
return psiElement;
}
// property could be in a super interface and implemented via a delegate
for (IType anInterface : ownersType.getInterfaces()) {
psiElement = resolveProperty(anInterface, propertyInfo.getName(), context);
if (psiElement != null) {
return psiElement;
}
}
return null;
}
public static PsiElement resolveProperty(IType ownersType, String propertyName, PsiElement context) {
ownersType = TypeUtil.getConcreteType(ownersType);
PsiElement resolved = null;
if (IGosuClass.ProxyUtil.isProxyClass(ownersType.getName())) {
PsiElement proxiedPsiClass = PsiTypeResolver.getProxiedPsiClass(ownersType, context);
if (proxiedPsiClass instanceof PsiClass) {
return findInheritedPropertyAcrossProxy((PsiClass) proxiedPsiClass,propertyName, context);
}
} else if ("outer".equals(propertyName)) {
resolved = PsiTypeResolver.resolveType(TypeUtil.getTrueEnclosingType(ownersType), context);
} else {
PsiElement psiClass = PsiTypeResolver.resolveType(ownersType, context);
if (psiClass instanceof PsiClass) {
resolved = resolveProperty(propertyName, (PsiClass) psiClass, context);
}
}
return resolved;
}
@Nullable
private static PsiElement findInheritedPropertyAcrossProxy(@Nullable PsiClass targetClass, @NotNull String targetPropertyName, @NotNull PsiElement ctx) {
// TODO: use GosuProperties class somehow
if (targetClass == null) {
return null;
}
boolean bSetter = ctx instanceof GosuReferenceExpressionImpl && ((GosuReferenceExpressionImpl) ctx).isAssigned();
PsiMethod candidate = null;
if (bSetter) {
String propName = targetPropertyName.startsWith("set") ? targetPropertyName.substring(3) : targetPropertyName;
String setPropertyName = "set" + propName;
for (PsiMethod method : targetClass.getMethods()) {
if (method.getName().equals(setPropertyName) && method.getParameterList().getParametersCount() == 1) {
return method;
}
if (method.getName().equalsIgnoreCase(setPropertyName) && method.getParameterList().getParametersCount() == 1) {
candidate = method;
}
}
} else {
String propName = targetPropertyName.startsWith("get") ? targetPropertyName.substring(3) :
targetPropertyName.startsWith("is") ? targetPropertyName.substring(2) : targetPropertyName;
String getPropertyName = "get" + propName;
String isPropertyName = "is" + propName;
for (PsiMethod method : targetClass.getMethods()) {
String methodName = method.getName();
if ((getPropertyName.equals(methodName) || isPropertyName.equals(methodName)) && method.getParameterList().getParametersCount() == 0) {
return method;
}
if ((getPropertyName.equalsIgnoreCase(methodName) || isPropertyName.equalsIgnoreCase(methodName)) && method.getParameterList().getParametersCount() == 0) {
candidate = method;
}
}
}
if (candidate != null) {
return candidate;
}
return findInheritedPropertyAcrossProxy(targetClass.getSuperClass(), targetPropertyName, ctx);
}
@Nullable
public static PsiElement resolveProperty(String strProperty, @Nullable PsiClass psiClass, @NotNull PsiElement ctx) {
// TODO: use GosuProperties class somehow
boolean bSetter = (ctx instanceof GosuReferenceExpressionImpl) && ((GosuReferenceExpressionImpl) ctx).isAssigned();
if (psiClass == null) {
return null;
}
if (bSetter) {
String strSetProperty = "set" + strProperty;
for (PsiMethod method : psiClass.getMethods()) {
if (method.getName().equals(strSetProperty) &&
method.getParameterList().getParametersCount() == 1) {
return method;
}
}
} else {
String strGetProperty = "get" + strProperty;
String strIsProperty = "is" + strProperty;
for (PsiMethod method : psiClass.getMethods()) {
if ((method.getName().equals(strGetProperty) || method.getName().equals(strIsProperty)) &&
method.getParameterList().getParametersCount() == 0) {
return method;
}
}
}
for (PsiMethod method : psiClass.getMethods()) {
if (method instanceof GosuMethodImpl &&
((GosuMethodImpl)method).isForProperty() &&
method.getName().equals(strProperty) &&
method.getParameterList().getParametersCount() == (bSetter ? 1 : 0)) {
return method;
}
}
for (PsiField field : psiClass.getFields()) {
// Try field name first
String strPropertyName = field.getName();
if (strPropertyName != null && strPropertyName.equals(strProperty)) {
return field;
}
if (field instanceof IGosuField) {
// Then try aliased property name
strPropertyName = ((IGosuField) field).getPropertyName();
if (strPropertyName != null && strPropertyName.equals(strProperty)) {
return ((IGosuField) field).getPropertyElement();
}
}
}
return null;
}
@Nullable
private static PsiField findInheritedFieldAcrossProxy(@Nullable PsiClass targetClass, String targetFieldName) {
PsiField result = null;
if (targetClass != null) {
result = targetClass.findFieldByName(targetFieldName, false);
if (result == null) {
result = findInheritedFieldAcrossProxy(targetClass.getSuperClass(), targetFieldName);
}
}
return result;
}
@Nullable
@Override
public PsiElement resolve(@NotNull IReducedSymbol symbol, @Nullable IGosuClass gsClass,final @NotNull GosuReferenceExpressionImpl expression) {
if (symbol.getType() instanceof INamespaceType) {
String packageName = symbol.getType().getName();
PsiPackage aPackage = JavaPsiFacade.getInstance(expression.getProject()).findPackage(packageName);
return (aPackage == null || !aPackage.isValid()) ? null : aPackage;
}
if (gsClass == null) {
return null;
}
if (symbol.isValueBoxed() && gsClass.isAnonymous()) {
gsClass = (IGosuClass) gsClass.getEnclosingType();
}
Class symClass = symbol.getClass();
IType symbolType = symbol.getType();
if (symbolType instanceof IMetaType) {
IType trueSymbolType = ((IMetaType) symbolType).getType();
if (trueSymbolType instanceof ITypeVariableType) {
return PsiTypeResolver.resolveTypeVariable((ITypeVariableType) trueSymbolType, expression);
}
}
if (symbol instanceof ITypedSymbol) {
SymbolType symbolKind = ((ITypedSymbol) symbol).getSymbolType();
switch (symbolKind) {
case CATCH_VARIABLE:
return resolveCatchLocalVar(symbol.getName(), expression);
case OBJECT_INITIALIZER:
return resolveProperty(symbol.getName(), ((IInitializerSymbol)symbol).getDeclaringTypeOfProperty(), expression);
case NAMED_PARAMETER:
return resolveNamedParameter(symbol.getName(), expression);
case PARAMETER_DECLARATION:
case FOREACH_VARIABLE:
return resolveLocalVar(symbol.getName(), expression);
default:
return null;
// throw new RuntimeException("Unsupported typed symbol " + symbolKind);
}
}
if (symbolType instanceof IMetaType) {
final IType trueSymbolType = ((IMetaType) symbolType).getType();
if (trueSymbolType instanceof ITypeVariableType) {
return PsiTypeResolver.resolveTypeVariable((ITypeVariableType) trueSymbolType, expression);
}
}
String referenceName = expression.getReferenceName();
if (IDynamicPropertySymbol.class.isAssignableFrom(symClass)) {
if (Keyword.KW_outer.equals(symbol.getName())) {
// 'outer'
return resolveOuter(gsClass, expression);
}
// Property reference
if (!symbol.isStatic()) {
IGosuClass symbolGosuClass = gsClass;
if (isMemberOnEnclosingType(gsClass, symbolGosuClass)) {
// Instance field from 'outer'
return resolveProperty(referenceName, symbolGosuClass.getEnclosingType(), expression);
} else {
// Instance field from 'this'
// return resolveProperty( getReferenceName(), symbolGosuClass );
return resolveProperty(symbol.getName(), symbolGosuClass, expression);
}
} else {
// Static field
return resolveProperty(referenceName, gsClass, expression);
}
}
if (IDynamicSymbol.class.isAssignableFrom(symClass)) {
// Instance or Static field
IGosuClass symbolGosuClass = gsClass;
if (!symbol.isStatic()) {
if (isMemberOnEnclosingType(gsClass, symbolGosuClass)) {
// Instance field from 'outer'
return resolveField(referenceName, symbolGosuClass.getEnclosingType(), expression);
} else {
// Instance field from 'this'
return resolveField(referenceName, symbolGosuClass, expression);
}
} else {
// Static field
return resolveField(referenceName, gsClass, expression);
}
}
PsiElement ret = null;
if (ICapturedSymbol.class.isAssignableFrom(symClass)) {
ret = resolveField(referenceName, gsClass, expression);
}
if (ret == null && symbol.getIndex() >= 0) {
// Local var
if (symbol.isValueBoxed()) {
// Local var is captured in an anonymous inner class.
ret = resolveField(referenceName, gsClass, expression);
}
if (ret == null) {
// Simple local var
ret = resolveLocalVar(referenceName, expression);
// Maybe a template param directive var
if (couldBeTemplateParamDirective(symbol, gsClass, ret)) {
return resolveTemplateParameter(symbol.getName(), expression);
}
}
return ret;
}
return null;
}
public static PsiElement resolveProperty(String strProperty, @NotNull IType type, PsiElement ctx) {
ITypeInfo typeInfo = type.getTypeInfo();
IPropertyInfo pi;
if( typeInfo instanceof IRelativeTypeInfo ) {
pi = ((IRelativeTypeInfo) typeInfo).getProperty(type, strProperty);
}
else {
pi = typeInfo.getProperty(strProperty);
}
return pi != null ? PsiFeatureResolver.resolveProperty(pi, ctx) : null;
}
public static PsiElement resolveField(String strField, @NotNull IType type, PsiElement ctx) {
final ITypeInfo typeInfo = type.getTypeInfo();
final IPropertyInfo property;
if (typeInfo instanceof IRelativeTypeInfo) {
property = ((IRelativeTypeInfo) typeInfo).getProperty(type, strField);
} else {
property = typeInfo.getProperty(strField);
}
return property == null ? null : PsiFeatureResolver.resolveProperty(property, ctx);
}
@Nullable
private PsiElement resolveCatchLocalVar(String name, @NotNull PsiElement context) {
if (context.getParent() instanceof GosuBaseElementImpl) {
GosuBaseElementImpl element = (GosuBaseElementImpl) context.getParent();
while (element != null) {
if (element.getGosuElementType().equals(GosuElementTypes.ELEM_TYPE_CatchClause)) {
GosuVariableImpl var = (GosuVariableImpl) element.getNode().findChildByType(GosuElementTypes.ELEM_TYPE_LocalVarDeclaration).getPsi();
if (var.getName().equals(name)) {
final PsiElement maybeInjectedElement = InjectedElementEditor.getOriginalElement(var);
return maybeInjectedElement == null ? var : PsiTreeUtil.getParentOfType(maybeInjectedElement, var.getClass(),false);
}
}
if( element.getParent() instanceof GosuBaseElementImpl ) {
element = (GosuBaseElementImpl) element.getParent();
} else {
element = null;
}
}
}
return null;
}
@Nullable
private PsiElement resolveNamedParameter(String paramName, @NotNull PsiElement context) {
PsiElement parent = context.getParent();
if (parent.getNode().getElementType() == GosuElementTypes.ELEM_TYPE_ArgumentListClause) {
parent = parent.getParent();
}
IFeatureInfo info = null;
if (parent instanceof GosuMethodCallExpressionImpl) {
IMethodCallExpression parsedElement = ((GosuMethodCallExpressionImpl) parent).getParsedElement();
IFunctionType functionType = parsedElement.getFunctionType();
if (functionType != null) {
info = functionType.getMethodOrConstructorInfo();
}
} else if (parent instanceof GosuNewExpressionImpl) {
INewExpression parsedElement = ((GosuNewExpressionImpl) parent).getParsedElement();
info = parsedElement.getConstructor();
}
if (info != null) {
PsiElement element = PsiFeatureResolver.resolveMethodOrConstructor((IHasParameterInfos) info, context);
if (element instanceof PsiMethod) {
PsiParameter[] parameters = ((PsiMethod) element).getParameterList().getParameters();
for (PsiParameter parameter : parameters) {
if (parameter.getName().equals(paramName)) {
return parameter;
}
}
} else if (element instanceof PsiClass && ((PsiClass) element).isAnnotationType()) {
PsiMethod[] methodsByName = ((PsiClass) element).findMethodsByName(paramName, false);
return methodsByName.length != 0 ? methodsByName[0] : null;
}
}
return null;
}
private boolean couldBeTemplateParamDirective(IReducedSymbol symbol, IGosuClass gsClass, @Nullable PsiElement ret) {
return ret == null && symbol instanceof ITypedSymbol && gsClass instanceof ITemplateType;
}
@Nullable
private PsiElement resolveLocalVar(@Nullable String strName, @NotNull GosuReferenceExpressionImpl expr) {
if (strName != null) {
PsiElement context = expr.getContext();
if (context != null) {
final PsiVariable psiVariable = JavaPsiFacadeUtil.getResolveHelper(context.getProject()).resolveReferencedVariable(strName, context);
final PsiElement maybeInjectedElement = psiVariable == null ? null : InjectedElementEditor.getOriginalElement(psiVariable);
return maybeInjectedElement == null ? null : PsiTreeUtil.getParentOfType(maybeInjectedElement, psiVariable.getClass(),false);
}
}
return null;
}
protected PsiElement resolveOuter(IGosuClass gsClass, PsiElement context) {
IType namedEnclosingType = gsClass instanceof IBlockClass ? TypeUtil.getTrueEnclosingType(gsClass) : gsClass;
return PsiTypeResolver.resolveType(TypeUtil.getTrueEnclosingType(namedEnclosingType), context);
}
protected boolean isMemberOnEnclosingType(@NotNull IGosuClass gsClass, IGosuClass symbolClass) {
if (!gsClass.isStatic() && gsClass.getEnclosingType() != null) {
return false;
}
// If the symbol is on this class, or any ancestors, it's not enclosed
//noinspection SuspiciousMethodCalls
//IType symbolClass = maybeUnwrapProxy(symbol.getGosuClass());
if (gsClass.getAllTypesInHierarchy().contains(symbolClass)) {
return false;
}
ICompilableType enclosingClass = gsClass.getEnclosingType();
while (enclosingClass != null) {
//noinspection SuspiciousMethodCalls
if (enclosingClass.getAllTypesInHierarchy().contains(symbolClass)) {
return true;
}
enclosingClass = enclosingClass.getEnclosingType();
}
return false;
}
@Nullable
private PsiElement resolveTemplateParameter(String name, PsiElement context) {
PsiElement c = context;
while (!(c instanceof GosuClassDefinitionImpl)) {
c = c.getParent();
}
for (GosuDirectiveExpressionImpl child : filter(Arrays.asList(c.getChildren()), GosuDirectiveExpressionImpl.class)) {
for (IGosuParameter parameter : child.getParameters()) {
if (parameter.getName().equals(name)) {
return parameter;
}
}
}
return null;
}
}