/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.completion.proposals; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameter; import gw.lang.parser.IBlockClass; import gw.lang.parser.ICapturedSymbol; import gw.lang.parser.IDynamicFunctionSymbol; import gw.lang.parser.IDynamicPropertySymbol; import gw.lang.parser.IDynamicSymbol; import gw.lang.parser.IReducedSymbol; import gw.lang.parser.ISymbol; import gw.lang.parser.Keyword; import gw.lang.parser.statements.IClassFileStatement; import gw.lang.reflect.IAttributedFeatureInfo; import gw.lang.reflect.IFeatureInfo; import gw.lang.reflect.IType; import gw.lang.reflect.gs.ICompilableType; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuProgram; import gw.plugin.ij.lang.psi.api.statements.IGosuField; import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl; import gw.plugin.ij.lang.psi.impl.expressions.GosuIdentifierExpressionImpl; import gw.plugin.ij.lang.psi.impl.resolvers.PsiTypeResolver; import gw.plugin.ij.util.JavaPsiFacadeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class SymbolCompletionProposal extends GosuCompletionProposal { private final ISymbol _symbol; private PsiElement context; public SymbolCompletionProposal(ISymbol symbol, int weight) { _symbol = symbol; setWeight(weight); } @Override public String toString() { return _symbol.getDisplayName(); } public ISymbol getSymbol() { return _symbol; } @Nullable @Override public PsiElement resolve(@NotNull PsiElement context) { this.context = context; PsiFile psiFile = context.getContainingFile().getOriginalFile(); IClassFileStatement classFileStatement = ((AbstractGosuClassFileImpl) psiFile).getParseData().getClassFileStatement(); IGosuClass gsClass = classFileStatement.getGosuClass(); if ((Keyword.KW_this.equals(_symbol.getName()) || Keyword.KW_super.equals(_symbol.getName())) && // 'this' must be an external symbol when in a program e.g., studio debugger expression (!(gsClass instanceof IGosuProgram) || gsClass.isAnonymous())) { if (gsClass instanceof IBlockClass) { while (gsClass instanceof IBlockClass) { gsClass = (IGosuClass) gsClass.getEnclosingType(); } } return PsiTypeResolver.resolveType(gsClass, this.context); } else if (Keyword.KW_outer.equals(_symbol.getName())) { // 'outer' return resolveOuter(gsClass); } else { return resolveSymbol(_symbol, gsClass); } } @Nullable @Override public IFeatureInfo getFeatureInfo() { if (_symbol instanceof IDynamicFunctionSymbol) { return ((IDynamicFunctionSymbol) _symbol).getMethodOrConstructorInfo(); } if (_symbol instanceof IDynamicPropertySymbol) { return ((IDynamicPropertySymbol) _symbol).getPropertyInfo(); } return null; } @Override public String getGenericName() { return _symbol.getName(); } @Nullable protected PsiElement resolveOuter(IGosuClass gsClass) { while (gsClass instanceof IBlockClass) { gsClass = (IGosuClass) gsClass.getEnclosingType(); } if (gsClass == null || gsClass.getEnclosingType() == null) { return null; } return PsiTypeResolver.resolveType(gsClass.getEnclosingType(), context); } //TODO-dp this duplicates logic in GosuIdentifierExpressionImpl @Nullable protected PsiElement resolveSymbol(@NotNull IReducedSymbol symbol, @NotNull IGosuClass gsClass) { Class symClass = symbol.getClass(); if (gsClass.getExternalSymbol(getReferenceName()) != null) { // Currently we do not attempt to link to decl source of external symbols return null; } if (IDynamicFunctionSymbol.class.isAssignableFrom(symClass)) { if (!symbol.isStatic()) { if (isMemberOnEnclosingType(symbol, gsClass)) { // Instance field from 'outer' return resolveFunction(getReferenceName(), getSymbolType(symbol).getEnclosingType()); } else { // Instance field from 'this' IType symbolType = getSymbolType(symbol); if(symbolType != null && getReferenceName() != null) { return resolveFunction(getReferenceName(), symbolType); } } } else { // Static field return resolveFunction(getReferenceName(), getSymbolType(symbol)); } } else if (IDynamicSymbol.class.isAssignableFrom(symClass)) { // Instance or Static field if (!symbol.isStatic()) { if (isMemberOnEnclosingType(symbol, gsClass)) { // Instance field from 'outer' return resolveField(getReferenceName(), getSymbolType(symbol).getEnclosingType()); } else { // Instance field from 'this' return resolveField(getReferenceName(), getSymbolType(symbol)); } } else { // Static field return resolveField(getReferenceName(), getSymbolType(symbol)); } } else if (ICapturedSymbol.class.isAssignableFrom(symClass)) { return resolveCapture(getReferenceName(), gsClass); } else if (symbol.getIndex() >= 0) { // Local var if (symbol.isValueBoxed()) { // Local var is captured in an anonymous inner class. // Symbol's value maintained as a one elem array of symbol's type. return resolveCapture(getReferenceName(), gsClass); } else { // Simple local var return resolveLocalVar(getReferenceName()); } } else if (IDynamicPropertySymbol.class.isAssignableFrom(symClass)) { throw new UnsupportedOperationException("Men at work"); } return null; } private IType getSymbolType(@NotNull IReducedSymbol symbol) { return GosuIdentifierExpressionImpl.maybeUnwrapProxy(symbol.getGosuClass()); } protected boolean isMemberOnEnclosingType(@NotNull IReducedSymbol symbol, @NotNull IGosuClass gsClass) { 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 = GosuIdentifierExpressionImpl.maybeUnwrapProxy(getSymbolType(symbol)); 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 resolveCapture(String strName, @NotNull IGosuClass gsClass) { return resolveField(strName, gsClass); } @Nullable protected PsiElement resolveField(String strField, @NotNull IType gsClass) { return resolveField(strField, PsiTypeResolver.resolveType(gsClass, context)); } @Nullable protected PsiElement resolveField(String strField, @Nullable PsiElement psiClass) { if (psiClass instanceof PsiClass) { final PsiField field = ((PsiClass)psiClass).findFieldByName(strField, false); return field != null ? field : resolveProperty(strField, ((PsiClass)psiClass)); } return null; } @Nullable protected PsiElement resolveFunction(String strField, @NotNull IType gsClass) { return resolveFunction(strField, PsiTypeResolver.resolveType(gsClass, context)); } @Nullable protected PsiElement resolveFunction(String strField, @NotNull PsiElement psiClass) { if (psiClass instanceof PsiClass) { for (PsiMethod method : ((PsiClass)psiClass).getAllMethods()) { if (makeMethodName(method).equals(strField)) { return method; } } } return null; } private String makeMethodName(@NotNull PsiMethod method) { StringBuilder sb = new StringBuilder(method.getName()).append("("); PsiParameter[] parameters = method.getParameterList().getParameters(); for (int i = 0; i < parameters.length; i++) { if (i != 0) { sb.append(", "); } sb.append(parameters[i].getType().getCanonicalText()); } return sb.append(")").toString(); } @Nullable protected PsiElement resolveProperty(String strProperty, String strFqn) { return resolveProperty(strProperty, PsiTypeResolver.resolveType(strFqn, context)); } @Nullable protected PsiElement resolveProperty(String strProperty, @NotNull PsiClass psiClass) { //## todo: check for this ref expr being the lhs of an assignment and look for setter instead for (PsiMethod method : psiClass.getMethods()) { if (method.getName().equals(strProperty) && method.getParameterList().getParametersCount() == 0) { return method; } } for (PsiField field : psiClass.getFields()) { if (field instanceof IGosuField) { IGosuField gsField = (IGosuField) field; String strPropertyName = gsField.getPropertyName(); if (strPropertyName != null && strPropertyName.equals(strProperty)) { return ((IGosuField) field).getPropertyElement(); } } } 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; } } return null; } @Nullable private PsiElement resolveLocalVar(String strName) { return resolveLocal(strName); } @Nullable protected PsiElement resolveLocal(@Nullable String strLocalVar) { if (strLocalVar != null) { if (context != null) { return JavaPsiFacadeUtil.getResolveHelper(context.getProject()).resolveReferencedVariable(strLocalVar, context); } } return null; } private String getReferenceName() { return _symbol.getName(); } }