/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.parameterinfo; import com.google.common.collect.Lists; import com.intellij.injected.editor.VirtualFileWindow; import com.intellij.lang.ASTNode; import com.intellij.lang.parameterInfo.CreateParameterInfoContext; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl; import gw.lang.parser.*; import gw.lang.parser.expressions.IBeanMethodCallExpression; import gw.lang.parser.expressions.IMethodCallExpression; import gw.lang.parser.expressions.INewExpression; import gw.lang.parser.expressions.ISynthesizedMemberAccessExpression; import gw.lang.reflect.*; import gw.plugin.ij.lang.parser.GosuBlockInvocationImpl; import gw.plugin.ij.lang.parser.GosuElementTypes; import gw.plugin.ij.lang.psi.api.IGosuResolveResult; import gw.plugin.ij.lang.psi.impl.GosuBaseElementImpl; import gw.plugin.ij.lang.psi.impl.GosuResolveResultImpl; import gw.plugin.ij.lang.psi.impl.expressions.*; import gw.plugin.ij.lang.psi.impl.resolvers.PsiFeatureResolver; import gw.plugin.ij.lang.psi.impl.resolvers.PsiTypeResolver; import gw.plugin.ij.util.FileUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ParameterInfoUtil { private static final Logger LOG = Logger.getInstance(ParameterInfoUtil.class); public static IType getActualType(IType type) { if (type instanceof IMetaType) { type = ((IMetaType) type).getType(); } type = TypeSystem.getPureGenericType(type); return type; } @Nullable public static PsiElement findParamOwnerAtOffset(int offset, @NotNull PsiFile file) { PsiElement element = file.findElementAt(offset); if (element == null) { return null; } while (!isParamOwner(element)) { element = element.getParent(); if (element instanceof PsiFile) { return null; } } return element.getParent(); } private static boolean isParamOwner(@NotNull PsiElement psiElement) { final PsiElement parent = psiElement.getParent(); if (parent instanceof GosuNewExpressionImpl || parent instanceof GosuMethodCallExpressionImpl || parent instanceof GosuBeanMethodCallExpressionImpl || parent instanceof GosuBlockInvocationImpl || parent instanceof GosuPropertyMemberAccessExpressionImpl || parent instanceof GosuIdentifierExpressionImpl) { final List<ASTNode> children = Arrays.asList(parent.getNode().getChildren(null)); final int index = children.indexOf(psiElement.getNode()); for (int i = index; i >= 0; i--) { final ASTNode astNode = children.get(i); if (astNode instanceof LeafPsiElement && astNode.getText().equals("(")) { return true; } } } return false; } @Nullable public static String verify(@NotNull CreateParameterInfoContext context) { final Object[] items = context.getItemsToShow(); if (items != null) { for (Object item : items) { if (item == null) { return "Items to show contains a null element."; } } } return null; } public static int getCurrentParameterIndex(@NotNull PsiElement parameterOwner, int offset) { ASTNode argList = parameterOwner.getNode().findChildByType(GosuElementTypes.ELEM_TYPE_ArgumentListClause); if (argList == null) { return 0; } ASTNode[] children = argList.getChildren(null); if (children.length == 0) { return 0; } int index = 0; int parameterStartOffset = 0; int curOffset = children[0].getStartOffset(); for (int i = 0; i < children.length; i++) { ASTNode child = children[i]; curOffset += child.getTextLength(); if (offset < curOffset) { break; } if (child.getText().equals(",")) { parameterStartOffset = i + 1; index++; } } while (parameterStartOffset < children.length && (children[parameterStartOffset] instanceof PsiWhiteSpaceImpl || children[parameterStartOffset].getElementType() == GosuElementTypes.ELEM_TYPE____UnhandledParsedElement)) { parameterStartOffset++; } if (children[parameterStartOffset].getText().equals(":")) { final String parameterName = children[parameterStartOffset + 1].getText(); ParamInfoContext paramInfoContext = ParameterInfoUtil.getContextForParamInfo(parameterOwner); Integer paramIndex = paramInfoContext.getParameterIndex(parameterName); if (paramIndex != -1) { index = paramIndex; } } return index; } @NotNull public static List<? extends IMethodInfo> getMethods(@NotNull IType type) { List<IMethodInfo> methods = new ArrayList<>(); do { ITypeInfo typeInfo = type.getTypeInfo(); if (typeInfo instanceof IRelativeTypeInfo) { methods.addAll(((IRelativeTypeInfo) typeInfo).getMethods(type)); } else { methods.addAll(typeInfo.getMethods()); } type = type.getEnclosingType(); } while (type != null); return methods; } public static List<? extends IConstructorInfo> getConstructors(@NotNull IType type) { ITypeInfo typeInfo = type.getTypeInfo(); if (typeInfo instanceof IRelativeTypeInfo) { return ((IRelativeTypeInfo) typeInfo).getConstructors(type); } else { return typeInfo.getConstructors(); } } static abstract class ParamInfoContext { protected final IType _rootType; protected IType _ownersType; protected String _signature; protected String[] _parameterNames; public ParamInfoContext(IType ownersType, String signature, String[] parameterNames, IType rootType) { _rootType = rootType; _ownersType = ownersType; _signature = signature; _parameterNames = parameterNames; } @Nullable public abstract IGosuResolveResult addSignatures(PsiFile file, List<IGosuResolveResult> list); public Integer getParameterIndex(String parameterName) { for (int i = 0; i < _parameterNames.length; i++) { if (_parameterNames[i].equals(parameterName)) { return i; } } throw new RuntimeException("Cannot find index of parameter " + parameterName); } } static class MethodParamInfoContext extends ParamInfoContext { protected String _methodName; public MethodParamInfoContext(IType ownersType, String methodName, String signature, String[] parameterNames, IType rootType) { super(ownersType, signature, parameterNames, rootType); _methodName = methodName; } @Nullable @Override public IGosuResolveResult addSignatures(@NotNull PsiFile file, @NotNull List<IGosuResolveResult> list) { final List<IMethodInfo> matchingMethods = Lists.newArrayList(); for (IMethodInfo method : ParameterInfoUtil.getMethods(_rootType)) { if (method.getDisplayName().equals(_methodName)) { matchingMethods.add(method); } } if (_ownersType instanceof IMetaType) { _ownersType = ((IMetaType) _ownersType).getType(); } _ownersType = TypeSystem.getPureGenericType(_ownersType); PsiElement c = PsiTypeResolver.resolveType(_ownersType, file); if (c == null) { return null; } IGosuResolveResult highlighted = null; for (IMethodInfo mi : matchingMethods) { IGosuResolveResult result = PsiFeatureResolver.resolveMethodOrConstructorWithSubstitutor(mi, c); if (result == null) { result = new GosuResolveResultImpl(null, true, mi); } list.add(result); if (equalsNoWhitespace(mi.getName(), _signature)) { highlighted = result; } } return highlighted; } private boolean equalsNoWhitespace(@NotNull String s1, @NotNull String s2) { return strip(s1).equals(strip(s2)); } public String strip(@NotNull String s) { StringBuilder builder = new StringBuilder(s.length()); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (!Character.isWhitespace(c)) { builder.append(c); } } return builder.toString(); } } private static String[] getParameterNames(IHasParameterInfos info) { IParameterInfo[] parameters = info.getParameters(); String[] params = new String[parameters.length]; for (int i = 0; i < parameters.length; i++) { params[i] = parameters[i].getName(); } return params; } private static class ConstructorParamInfoContext extends ParamInfoContext { public ConstructorParamInfoContext(IConstructorInfo constructorInfo, IType rootType) { super(constructorInfo.getOwnersType(), constructorInfo.getName(), getParameterNames(constructorInfo), rootType); } @Nullable @Override public IGosuResolveResult addSignatures(@NotNull PsiFile file, @NotNull List<IGosuResolveResult> list) { final PsiElement element = PsiTypeResolver.resolveType(ParameterInfoUtil.getActualType(_rootType), file); if (element == null) { return null; } IGosuResolveResult highlightedMethod = null; for (IConstructorInfo ci : ParameterInfoUtil.getConstructors(_rootType)) { IGosuResolveResult result = PsiFeatureResolver.resolveMethodOrConstructorWithSubstitutor(ci, element); if (result == null) { result = new GosuResolveResultImpl(null, true, ci); } list.add(result); if (ci.getName().equals(_signature)) { highlightedMethod = result; } } return highlightedMethod; } } @Nullable public static ParamInfoContext getContextForParamInfo(@NotNull PsiElement owner) { IParsedElement pe = ((GosuBaseElementImpl) owner).getParsedElement(); if (pe instanceof ISynthesizedMemberAccessExpression) { ISynthesizedMemberAccessExpression expression = (ISynthesizedMemberAccessExpression) pe; IType rootType = expression.getRootType(); String methodName = expression.getMethodNameForSyntheticAccess(); IMethodInfo method = rootType.getTypeInfo().getMethod(methodName); if (method != null) { return new MethodParamInfoContext(method.getOwnersType(), methodName, method.getName(), getParameterNames(method), rootType); } } else if (pe instanceof IBeanMethodCallExpression) { IBeanMethodCallExpression expression = (IBeanMethodCallExpression) pe; IType rootType = expression.getRootType(); if (rootType instanceof IMetaType) { rootType = ((IMetaType) rootType).getType(); } IMethodInfo method = expression.getMethodDescriptor(); if (method != null) { String methodName = getMethodName(expression, method); return new MethodParamInfoContext(method.getOwnersType(), methodName, method.getName(), getParameterNames(method), rootType); } } else if (pe instanceof IMethodCallExpression) { IMethodCallExpression methodCallExpression = (IMethodCallExpression) pe; if (methodCallExpression.getFunctionSymbol() instanceof IConstructorFunctionSymbol) { IConstructorFunctionSymbol constructorFunctionSymbol = (IConstructorFunctionSymbol) methodCallExpression.getFunctionSymbol(); IConstructorInfo constructorInfo = constructorFunctionSymbol.getConstructorInfo(); if( constructorInfo != null ) { return new ConstructorParamInfoContext(constructorInfo, constructorInfo.getType()); } } else { IType invokingType = getInvokingType(owner); if (invokingType != null) { IMethodInfo method = getMethodInfo(methodCallExpression); if (method != null) { String methodName = getMethodName(methodCallExpression, method); return new MethodParamInfoContext(method.getOwnersType(), methodName, method.getName(), getParameterNames(method), invokingType); } else { IFunctionType functionType = methodCallExpression.getFunctionType(); if (functionType == null) { return null; } String methodName = getMethodName(methodCallExpression, functionType); final IType enclosingType = getEnclosingType(functionType); return enclosingType == null ? null : new MethodParamInfoContext( enclosingType, methodName, functionType.getParamSignature().toString(), functionType.getParameterNames(), invokingType); } } } } else if (pe instanceof INewExpression) { INewExpression newExpression = (INewExpression) pe; IConstructorInfo constructorInfo = newExpression.getConstructor(); return constructorInfo != null ? new ConstructorParamInfoContext(constructorInfo, newExpression.getType()) : null; } return null; } private static IType getEnclosingType(IFunctionType functionType) { IType enclosingType = functionType.getEnclosingType(); return enclosingType != null ? enclosingType : functionType.getScriptPart() != null ? functionType.getScriptPart().getContainingType() : null; } @Nullable private static IMethodInfo getMethodInfo(@NotNull IMethodCallExpression methodCallExpression) { final IFunctionType type = methodCallExpression.getFunctionType(); return type != null ? type.getMethodInfo() : null; } private static String getMethodName(IExpression expression, IFunctionType functionType) { return functionType == null ? findMethodName(expression) : functionType.getDisplayName(); } @Nullable private static String getMethodName(@NotNull IExpression expression, @Nullable IMethodInfo methodInfo) { return methodInfo == null ? findMethodName(expression) : methodInfo.getDisplayName(); } private static IType getInvokingType(PsiElement owner) { final PsiClass containingPsiClass = getContainingPsiClass(owner); final VirtualFile virtualFile = containingPsiClass.getContainingFile().getVirtualFile(); if (virtualFile instanceof VirtualFileWindow) { final VirtualFile delegate = ((VirtualFileWindow) virtualFile).getDelegate(); final String[] typesForFile = TypeSystem.getTypesForFile(TypeSystem.getGlobalModule(), FileUtil.toIFile(delegate)); final IType type = TypeSystem.getByFullNameIfValid(typesForFile[0]); return type; } else { final String qualifiedName = containingPsiClass.getQualifiedName(); return TypeSystem.getByFullNameIfValid(qualifiedName); } } @Nullable private static String findMethodName(@NotNull IExpression expression) { for (IToken token : expression.getTokens()) { if (token.getType() == ISourceCodeTokenizer.TT_WORD) { return token.getText(); } } return null; } @Nullable private static PsiClass getContainingPsiClass(@Nullable PsiElement owner) { if (owner == null) { return null; } if (owner instanceof PsiClass) { return (PsiClass) owner; } return getContainingPsiClass(owner.getParent()); } }