/* * Copyright 2009-2017 the original author or authors. * * 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 org.eclipse.jdt.groovy.search; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import groovyjarjarasm.asm.Opcodes; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.DynamicVariable; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.Variable; import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; import org.codehaus.groovy.ast.expr.BooleanExpression; import org.codehaus.groovy.ast.expr.ClassExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.GStringExpression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.NotExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter.StatementMeta; import org.codehaus.jdt.groovy.internal.compiler.ast.JDTMethodNode; import org.codehaus.jdt.groovy.model.GroovyCompilationUnit; import org.eclipse.jdt.groovy.core.util.GroovyUtils; import org.eclipse.jdt.groovy.core.util.ReflectionUtils; import org.eclipse.jdt.groovy.search.TypeLookupResult.TypeConfidence; import org.eclipse.jdt.groovy.search.VariableScope.VariableInfo; import org.eclipse.jdt.internal.compiler.lookup.LazilyResolvedMethodBinding; /** * Determines types using AST inspection. */ public class SimpleTypeLookup implements ITypeLookupExtension { protected GroovyCompilationUnit unit; public void initialize(GroovyCompilationUnit unit, VariableScope topLevelScope) { this.unit = unit; } public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNode objectExpressionType) { return lookupType(node, scope, objectExpressionType, false); } public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNode objectExpressionType, boolean isStaticObjectExpression) { TypeConfidence[] confidence = {TypeConfidence.EXACT}; if (ClassHelper.isPrimitiveType(objectExpressionType)) { objectExpressionType = ClassHelper.getWrapper(objectExpressionType); } ClassNode declaringType = objectExpressionType != null ? objectExpressionType : findDeclaringType(node, scope, confidence); TypeLookupResult result = findType(node, declaringType, scope, confidence[0], isStaticObjectExpression || (objectExpressionType == null && scope.isStatic()), objectExpressionType == null); return result; } public TypeLookupResult lookupType(FieldNode node, VariableScope scope) { return new TypeLookupResult(node.getType(), node.getDeclaringClass(), node, TypeConfidence.EXACT, scope); } public TypeLookupResult lookupType(MethodNode node, VariableScope scope) { return new TypeLookupResult(node.getReturnType(), node.getDeclaringClass(), node, TypeConfidence.EXACT, scope); } public TypeLookupResult lookupType(AnnotationNode node, VariableScope scope) { ClassNode baseType = node.getClassNode(); return new TypeLookupResult(baseType, baseType, baseType, TypeConfidence.EXACT, scope); } public TypeLookupResult lookupType(ImportNode node, VariableScope scope) { ClassNode baseType = node.getType(); if (baseType != null) { return new TypeLookupResult(baseType, baseType, baseType, TypeConfidence.EXACT, scope); } else { // this is a * import return new TypeLookupResult(VariableScope.OBJECT_CLASS_NODE, VariableScope.OBJECT_CLASS_NODE, VariableScope.OBJECT_CLASS_NODE, TypeConfidence.INFERRED, scope); } } /** * Returns the passed in node, unless the declaration of an InnerClassNode. */ public TypeLookupResult lookupType(ClassNode node, VariableScope scope) { ClassNode resultType; if (node instanceof InnerClassNode && !node.isRedirectNode()) { resultType = node.getSuperClass(); if (resultType.getName().equals(VariableScope.OBJECT_CLASS_NODE.getName()) && node.getInterfaces().length > 0) { resultType = node.getInterfaces()[0]; } } else { resultType = node; } return new TypeLookupResult(resultType, resultType, node, TypeConfidence.EXACT, scope); } public TypeLookupResult lookupType(Parameter node, VariableScope scope) { // look up the type in the current scope to see if the type has // has been predetermined (eg- for loop variables) VariableInfo info = scope.lookupNameInCurrentScope(node.getName()); ClassNode type; if (info != null) { type = info.type; } else { type = node.getType(); } return new TypeLookupResult(type, scope.getEnclosingTypeDeclaration(), node /* should be methodnode? */, TypeConfidence.EXACT, scope); } public void lookupInBlock(BlockStatement node, VariableScope scope) { } //-------------------------------------------------------------------------- protected ClassNode findDeclaringType(Expression node, VariableScope scope, TypeConfidence[] confidence) { if (node instanceof ClassExpression || node instanceof ConstructorCallExpression) { return node.getType(); } else if (node instanceof FieldExpression) { return ((FieldExpression) node).getField().getDeclaringClass(); } else if (node instanceof StaticMethodCallExpression) { return ((StaticMethodCallExpression) node).getOwnerType(); } else if (node instanceof ConstantExpression) { if (scope.isMethodCall()) { // method call with an implicit this return scope.getDelegateOrThis(); } } else if (node instanceof VariableExpression) { Variable var = ((VariableExpression) node).getAccessedVariable(); if (var instanceof DynamicVariable) { // search type hierarchy for declaration // first look in delegate and hierarchy and then go for this ASTNode declaration = null; ClassNode delegate = scope.getDelegate(); if (delegate != null) { declaration = findDeclaration(var.getName(), delegate, (scope.getWormhole().get("lhs") == node), false, scope.getMethodCallArgumentTypes()); } ClassNode thiz = scope.getThis(); if (declaration == null && thiz != null && (delegate == null || !thiz.equals(delegate))) { declaration = findDeclaration(var.getName(), thiz, (scope.getWormhole().get("lhs") == node), false, scope.getMethodCallArgumentTypes()); } ClassNode type; if (declaration == null) { // this is a dynamic variable that doesn't seem to have a declaration // it might be an unknown and a mistake, but it could also be declared by 'this' type = thiz != null ? thiz : VariableScope.OBJECT_CLASS_NODE; } else { type = getDeclaringTypeFromDeclaration(declaration, var.getType()); } confidence[0] = TypeConfidence.findLessPrecise(confidence[0], TypeConfidence.INFERRED); return type; } else if (var instanceof FieldNode) { return ((FieldNode) var).getDeclaringClass(); } else if (var instanceof PropertyNode) { return ((PropertyNode) var).getDeclaringClass(); } else if (VariableScope.isThisOrSuper((VariableExpression) node)) { // use 'node' because 'var' may be null // this or super expression, but it is not bound, probably because concrete ast was requested return scope.lookupName(((VariableExpression) node).getName()).declaringType; } // else local variable, no declaring type } return VariableScope.OBJECT_CLASS_NODE; } protected TypeLookupResult findType(Expression node, ClassNode declaringType, VariableScope scope, TypeConfidence confidence, boolean isStaticObjectExpression, boolean isPrimaryExpression) { MethodNode target; // use value from node metadata if it is available if (scope.isMethodCall() && (target = getMethodTarget(node)) != null) { return new TypeLookupResult(target.getReturnType(), target.getDeclaringClass(), target, confidence, scope); } if (node instanceof VariableExpression) { return findTypeForVariable((VariableExpression) node, scope, confidence, declaringType); } ClassNode nodeType = node.getType(); if ((!isPrimaryExpression || scope.isMethodCall()) && node instanceof ConstantExpression) { return findTypeForNameWithKnownObjectExpression(node.getText(), nodeType, declaringType, scope, confidence, isStaticObjectExpression, isPrimaryExpression, /*isLhsExpression:*/(scope.getWormhole().remove("lhs") == node)); } if (node instanceof ConstantExpression) { ConstantExpression cexp = (ConstantExpression) node; if (cexp.isNullExpression()) { return new TypeLookupResult(VariableScope.VOID_CLASS_NODE, null, null, confidence, scope); } else if (cexp.isTrueExpression() || cexp.isFalseExpression()) { return new TypeLookupResult(VariableScope.BOOLEAN_CLASS_NODE, null, null, confidence, scope); } else if (cexp.isEmptyStringExpression() || VariableScope.STRING_CLASS_NODE.equals(nodeType)) { return new TypeLookupResult(VariableScope.STRING_CLASS_NODE, null, node, confidence, scope); } else if (ClassHelper.isNumberType(nodeType) || ClassHelper.BigDecimal_TYPE.equals(nodeType) || ClassHelper.BigInteger_TYPE.equals(nodeType)) { return new TypeLookupResult(ClassHelper.isPrimitiveType(nodeType) ? ClassHelper.getWrapper(nodeType) : nodeType, null, null, confidence, scope); } else { return new TypeLookupResult(nodeType, null, null, TypeConfidence.UNKNOWN, scope); } } else if (node instanceof BooleanExpression || node instanceof NotExpression) { return new TypeLookupResult(VariableScope.BOOLEAN_CLASS_NODE, null, null, confidence, scope); } else if (node instanceof GStringExpression) { // return String not GString so that DGMs will apply return new TypeLookupResult(VariableScope.STRING_CLASS_NODE, null, null, confidence, scope); } else if (node instanceof BitwiseNegationExpression) { ClassNode type = ((BitwiseNegationExpression) node).getExpression().getType(); // check for ~/.../ (a.k.a. Pattern literal) if (VariableScope.STRING_CLASS_NODE.equals(type)) { return new TypeLookupResult(VariableScope.PATTERN_CLASS_NODE, null, null, confidence, scope); } return new TypeLookupResult(type, null, null, confidence, scope); } else if (node instanceof ClosureExpression && VariableScope.isPlainClosure(nodeType)) { ClassNode returnType = (ClassNode) node.getNodeMetaData("returnType"); if (returnType != null && !VariableScope.isVoidOrObject(returnType)) GroovyUtils.updateClosureWithInferredTypes(nodeType, returnType, ((ClosureExpression) node).getParameters()); } else if (node instanceof ClassExpression) { if (isClassLiteralExpression((ClassExpression) node, scope)) { ClassNode classType = ClassHelper.makeWithoutCaching(ClassHelper.CLASS_Type.getName()); classType.setGenericsTypes(new GenericsType[] {new GenericsType(node.getType())}); classType.setRedirect(ClassHelper.CLASS_Type); classType.setSourcePosition(node); return new TypeLookupResult(classType, null, node.getType(), TypeConfidence.EXACT, scope); } return new TypeLookupResult(nodeType, declaringType, nodeType, confidence, scope); } else if (node instanceof ConstructorCallExpression) { ConstructorCallExpression constructorCall = (ConstructorCallExpression) node; if (constructorCall.isThisCall()) { MethodNode constructorDecl = scope.getEnclosingMethodDeclaration(); // watch for initializers but no constructor declaringType = constructorDecl != null ? constructorDecl.getDeclaringClass() : scope.getEnclosingTypeDeclaration(); } else if (constructorCall.isSuperCall()) { declaringType = scope.getEnclosingMethodDeclaration().getDeclaringClass().getUnresolvedSuperClass(); } // try to find best match if there is more than one constructor to choose from List<ConstructorNode> declaredConstructors = declaringType.getDeclaredConstructors(); if (constructorCall.getArguments() instanceof ArgumentListExpression && declaredConstructors.size() > 1) { List<ConstructorNode> looseMatches = new ArrayList<ConstructorNode>(); List<ClassNode> callTypes = scope.getMethodCallArgumentTypes(); for (ConstructorNode ctor : declaredConstructors) { if (callTypes.size() == ctor.getParameters().length) { if (Boolean.TRUE.equals(isTypeCompatible(callTypes, ctor.getParameters()))) { return new TypeLookupResult(nodeType, declaringType, ctor, confidence, scope); } // argument types may not be fully resolved; at least the number of arguments matched looseMatches.add(ctor); } } if (!looseMatches.isEmpty()) { declaredConstructors = looseMatches; } } ASTNode declaration = !declaredConstructors.isEmpty() ? declaredConstructors.get(0) : declaringType; return new TypeLookupResult(nodeType, declaringType, declaration, confidence, scope); } else if (node instanceof StaticMethodCallExpression) { String methodName = ((StaticMethodCallExpression) node).getMethod(); ClassNode ownerType = ((StaticMethodCallExpression) node).getOwnerType(); List<MethodNode> candidates = new LinkedList<MethodNode>(); if (!ownerType.isInterface()) { candidates.addAll(ownerType.getMethods(methodName)); } else { LinkedHashSet<ClassNode> faces = new LinkedHashSet<ClassNode>(); VariableScope.findAllInterfaces(ownerType, faces, false); for (ClassNode face : faces) { candidates.addAll(face.getMethods(methodName)); } } for (Iterator<MethodNode> it = candidates.iterator(); it.hasNext();) { if (!it.next().isStatic()) it.remove(); } if (!candidates.isEmpty()) { MethodNode closestMatch; if (scope.isMethodCall()) { closestMatch = findMethodDeclaration0(candidates, scope.getMethodCallArgumentTypes()); confidence = TypeConfidence.INFERRED; } else { closestMatch = candidates.get(0); confidence = TypeConfidence.LOOSELY_INFERRED; } return new TypeLookupResult(closestMatch.getReturnType(), closestMatch.getDeclaringClass(), closestMatch, confidence, scope); } } if (!(node instanceof TupleExpression) && nodeType.equals(VariableScope.OBJECT_CLASS_NODE)) { confidence = TypeConfidence.UNKNOWN; } return new TypeLookupResult(nodeType, declaringType, null, confidence, scope); } /** * Looks for a name within an object expression. It is either in the hierarchy, it is in the variable scope, or it is unknown. */ protected TypeLookupResult findTypeForNameWithKnownObjectExpression(String name, ClassNode type, ClassNode declaringType, VariableScope scope, TypeConfidence confidence, boolean isStaticObjectExpression, boolean isPrimaryExpression, boolean isLhsExpression) { ClassNode realDeclaringType; VariableInfo varInfo; TypeConfidence origConfidence = confidence; ASTNode declaration = findDeclaration(name, declaringType, isLhsExpression, isStaticObjectExpression, scope.getMethodCallArgumentTypes()); if (declaration == null && isPrimaryExpression) { ClassNode thiz = scope.getThis(); if (thiz != null && !thiz.equals(declaringType)) { // probably in a closure where the delegate has changed declaration = findDeclaration(name, thiz, isLhsExpression, isStaticObjectExpression, scope.getMethodCallArgumentTypes()); } } // GRECLIPSE-1079 if (declaration == null && isStaticObjectExpression) { // we might have a reference to a property/method defined on java.lang.Class declaration = findDeclaration(name, VariableScope.CLASS_CLASS_NODE, isLhsExpression, isStaticObjectExpression, scope.getMethodCallArgumentTypes()); } if (declaration != null) { type = getTypeFromDeclaration(declaration, declaringType); realDeclaringType = getDeclaringTypeFromDeclaration(declaration, declaringType); } else if ("this".equals(name)) { // Fix for 'this' as property of ClassName declaration = declaringType; type = declaringType; realDeclaringType = declaringType; } else if (isPrimaryExpression && (varInfo = scope.lookupName(name)) != null) { // make everything from the scopes available // now try to find the declaration again type = varInfo.type; realDeclaringType = varInfo.declaringType; declaration = findDeclaration(name, realDeclaringType, isLhsExpression, isStaticObjectExpression, scope.getMethodCallArgumentTypes()); if (declaration == null) { declaration = varInfo.declaringType; } } else if (name.equals("call")) { // assume that this is a synthetic call method for calling a closure realDeclaringType = VariableScope.CLOSURE_CLASS_NODE; declaration = realDeclaringType.getMethods("call").get(0); } else { realDeclaringType = declaringType; confidence = TypeConfidence.UNKNOWN; } if (declaration != null && !realDeclaringType.equals(VariableScope.CLASS_CLASS_NODE)) { // check to see if the object expression is static but the declaration is not if (declaration instanceof FieldNode) { if (isStaticObjectExpression && !((FieldNode) declaration).isStatic()) { confidence = TypeConfidence.UNKNOWN; } } else if (declaration instanceof PropertyNode) { FieldNode underlyingField = ((PropertyNode) declaration).getField(); if (underlyingField != null) { // prefer looking at the underlying field if (isStaticObjectExpression && !underlyingField.isStatic()) { confidence = TypeConfidence.UNKNOWN; } } else if (isStaticObjectExpression && !((PropertyNode) declaration).isStatic()) { confidence = TypeConfidence.UNKNOWN; } } else if (declaration instanceof MethodNode) { if (isStaticObjectExpression && !((MethodNode) declaration).isStatic()) { confidence = TypeConfidence.UNKNOWN; } else { // check if the arguments and parameters are mismatched; a category method may make a better match if (((MethodNode) declaration).getParameters().length != scope.getMethodCallNumberOfArguments()) { confidence = TypeConfidence.LOOSELY_INFERRED; } } } } if (confidence == TypeConfidence.UNKNOWN && realDeclaringType.getName().equals(VariableScope.CLASS_CLASS_NODE.getName())) { // GRECLIPSE-1544 // check the type parameter for this class node reference // likely a type coming in from STC GenericsType[] classTypeParams = realDeclaringType.getGenericsTypes(); ClassNode typeParam = classTypeParams != null && classTypeParams.length == 1 ? classTypeParams[0].getType() : null; if (typeParam != null && !typeParam.getName().equals(VariableScope.CLASS_CLASS_NODE.getName()) && !typeParam.getName().equals(VariableScope.OBJECT_CLASS_NODE.getName())) { return findTypeForNameWithKnownObjectExpression(name, type, typeParam, scope, origConfidence, isStaticObjectExpression, isPrimaryExpression, isLhsExpression); } } return new TypeLookupResult(type, realDeclaringType, declaration, confidence, scope); } protected TypeLookupResult findTypeForVariable(VariableExpression var, VariableScope scope, TypeConfidence confidence, ClassNode declaringType) { ASTNode decl = var; ClassNode type = var.getType(); TypeConfidence newConfidence = confidence; Variable accessedVar = var.getAccessedVariable(); VariableInfo variableInfo = scope.lookupName(var.getName()); if (accessedVar instanceof ASTNode) { decl = (ASTNode) accessedVar; if (decl instanceof FieldNode || decl instanceof MethodNode || decl instanceof PropertyNode) { // use field/method/property info variableInfo = null; type = getTypeFromDeclaration(decl, ((AnnotatedNode) decl).getDeclaringClass()); } } else if (accessedVar instanceof DynamicVariable) { // likely a reference to a field or method in a type in the hierarchy; find the declaration ASTNode candidate = findDeclaration(accessedVar.getName(), getMorePreciseType(declaringType, variableInfo), (scope.getWormhole().remove("lhs") == var), false, scope.getMethodCallArgumentTypes()); if (candidate != null) { decl = candidate; declaringType = getDeclaringTypeFromDeclaration(decl, variableInfo != null ? variableInfo.declaringType : VariableScope.OBJECT_CLASS_NODE); } else { newConfidence = TypeConfidence.UNKNOWN; // dynamic variables are not allowed outside of script mainline if (variableInfo != null && !scope.inScriptRunMethod()) variableInfo = null; } type = getTypeFromDeclaration(decl, declaringType); } if (variableInfo != null && !(decl instanceof MethodNode)) { type = variableInfo.type; if (VariableScope.isThisOrSuper(var)) decl = type; declaringType = getMorePreciseType(declaringType, variableInfo); newConfidence = TypeConfidence.findLessPrecise(confidence, TypeConfidence.INFERRED); } return new TypeLookupResult(type, declaringType, decl, newConfidence, scope); } /** * Looks for the named member in the declaring type. Also searches super types. The result can be a field, method, or property. * * @param name the name of the field, method, constant or property to find * @param declaringType the type in which the named member's declaration resides * @param isLhsExpression {@code true} if named member is being assigned a value * @param isStaticExpression {@code true} if member is being accessed statically * @param methodCallArgumentTypes types of arguments to the associated method call (or {@code null} if not a method call) */ protected ASTNode findDeclaration(String name, ClassNode declaringType, boolean isLhsExpression, boolean isStaticExpression, List<ClassNode> methodCallArgumentTypes) { if (declaringType.isArray()) { // only length exists on arrays if (name.equals("length")) { return createLengthField(declaringType); } // otherwise search on object return findDeclaration(name, VariableScope.OBJECT_CLASS_NODE, isLhsExpression, isStaticExpression, methodCallArgumentTypes); } if (methodCallArgumentTypes != null) { ASTNode method = findMethodDeclaration(name, declaringType, methodCallArgumentTypes); if (method != null) { return method; } // name may still map to something that is callable; keep looking } // look for canonical accessor method MethodNode accessor = AccessorSupport.findAccessorMethodForPropertyName(name, declaringType, false, !isLhsExpression ? READER : WRITER); if (accessor != null && !isSynthetic(accessor) && (accessor.isStatic() == isStaticExpression)) { return accessor; } LinkedHashSet<ClassNode> typeHierarchy = new LinkedHashSet<ClassNode>(); VariableScope.createTypeHierarchy(declaringType, typeHierarchy, true); // look for property for (ClassNode type : typeHierarchy) { PropertyNode property = type.getProperty(name); if (property != null) { return property; } } // look for field FieldNode field = declaringType.getField(name); if (field != null) { return field; } typeHierarchy.clear(); VariableScope.findAllInterfaces(declaringType, typeHierarchy, true); // look for constant in interfaces for (ClassNode type : typeHierarchy) { if (type == declaringType) { continue; } field = type.getField(name); if (field != null && field.isFinal() && field.isStatic()) { return field; } } // look for static or synthetic accessor if (accessor != null) { return accessor; } if (methodCallArgumentTypes == null) { // reference may be in method pointer or static import; look for method as last resort return findMethodDeclaration(name, declaringType, null); } return null; } /** * Finds a method with the given name in the declaring type. Prioritizes methods * with the same number of arguments, but if multiple methods exist with same name, * then will return an arbitrary one. */ protected MethodNode findMethodDeclaration(String name, ClassNode declaringType, List<ClassNode> methodCallArgumentTypes) { // concrete types return all declared methods from getMethods(String) if (!declaringType.isInterface() && !declaringType.isAbstract()) { List<MethodNode> candidates = declaringType.getMethods(name); if (!candidates.isEmpty()) { return findMethodDeclaration0(candidates, methodCallArgumentTypes); } return null; } // abstract types may not return all methods from getMethods(String) LinkedHashSet<ClassNode> types = new LinkedHashSet<ClassNode>(); if (!declaringType.isInterface()) types.add(declaringType); VariableScope.findAllInterfaces(declaringType, types, true); types.add(ClassHelper.OBJECT_TYPE); // implicit super type MethodNode outerCandidate = null; for (ClassNode type : types) { MethodNode innerCandidate = null; List<MethodNode> candidates = type.getMethods(name); if (!candidates.isEmpty()) { innerCandidate = findMethodDeclaration0(candidates, methodCallArgumentTypes); if (outerCandidate == null) { outerCandidate = innerCandidate; } } if (innerCandidate != null && methodCallArgumentTypes != null) { Parameter[] methodParameters = innerCandidate.getParameters(); if (methodCallArgumentTypes.isEmpty() && methodParameters.length == 0) { return innerCandidate; } if (methodCallArgumentTypes.size() == methodParameters.length) { outerCandidate = innerCandidate; Boolean suitable = isTypeCompatible(methodCallArgumentTypes, methodParameters); if (Boolean.FALSE.equals(suitable)) { continue; } if (Boolean.TRUE.equals(suitable)) { return innerCandidate; } } } } return outerCandidate; } protected MethodNode findMethodDeclaration0(List<MethodNode> candidates, List<ClassNode> arguments) { // remember first entry in case exact match not found MethodNode closestMatch = candidates.get(0); if (arguments == null) { return closestMatch; } // prefer retrieving the method with the same number of args as specified in the parameter. // if none exists, or parameter is -1, then arbitrarily choose the first. for (Iterator<MethodNode> iterator = candidates.iterator(); iterator.hasNext();) { MethodNode maybeMethod = iterator.next(); Parameter[] parameters = maybeMethod.getParameters(); if (parameters.length == 0 && arguments.isEmpty()) { return maybeMethod.getOriginal(); } if (parameters.length == arguments.size()) { Boolean suitable = isTypeCompatible(arguments, parameters); if (Boolean.TRUE.equals(suitable)) { return maybeMethod.getOriginal(); } if (!Boolean.FALSE.equals(suitable)) { closestMatch = maybeMethod.getOriginal(); continue; // don't remove } } iterator.remove(); } return closestMatch; } //-------------------------------------------------------------------------- // TODO: Can any of these be relocated for reuse? protected static final AccessorSupport[] READER = {AccessorSupport.GETTER, AccessorSupport.ISSER}; protected static final AccessorSupport[] WRITER = {AccessorSupport.SETTER}; protected static ASTNode createLengthField(ClassNode declaringType) { FieldNode lengthField = new FieldNode("length", Opcodes.ACC_PUBLIC, VariableScope.INTEGER_CLASS_NODE, declaringType, null); lengthField.setType(VariableScope.INTEGER_CLASS_NODE); lengthField.setDeclaringClass(declaringType); return lengthField; } /** * @return target of method call expression if available or {@code null} */ protected static MethodNode getMethodTarget(Expression expr) { StatementMeta meta = (StatementMeta) expr.getNodeMetaData(StatementMeta.class); if (meta != null) { MethodNode target = (MethodNode) ReflectionUtils.getPrivateField(StatementMeta.class, "target", meta); return target; } // TODO: Is "((StaticMethodCallExpression) expr).getMetaMethod()" helpful? return null; } protected static ClassNode getMorePreciseType(ClassNode declaringType, VariableInfo info) { ClassNode maybeDeclaringType = info != null ? info.declaringType : VariableScope.OBJECT_CLASS_NODE; if (maybeDeclaringType.equals(VariableScope.OBJECT_CLASS_NODE) && !VariableScope.OBJECT_CLASS_NODE.equals(declaringType)) { return declaringType; } else { return maybeDeclaringType; } } /** * @param declaration the declaration to look up * @param resolvedType the unredirected type that declares this declaration somewhere in its hierarchy * @return class node with generics replaced by actual types */ protected static ClassNode getTypeFromDeclaration(ASTNode declaration, ClassNode resolvedType) { ClassNode typeOfDeclaration; if (declaration instanceof PropertyNode) { FieldNode field = ((PropertyNode) declaration).getField(); if (field != null) { declaration = field; } } if (declaration instanceof FieldNode) { FieldNode fieldNode = (FieldNode) declaration; typeOfDeclaration = fieldNode.getType(); if (VariableScope.OBJECT_CLASS_NODE.equals(typeOfDeclaration)) { // check to see if we can do better by looking at the initializer of the field if (fieldNode.hasInitialExpression()) { typeOfDeclaration = fieldNode.getInitialExpression().getType(); } } } else if (declaration instanceof MethodNode) { typeOfDeclaration = ((MethodNode) declaration).getReturnType(); } else if (declaration instanceof Expression) { typeOfDeclaration = ((Expression) declaration).getType(); } else { typeOfDeclaration = VariableScope.OBJECT_CLASS_NODE; } return typeOfDeclaration; } protected static ClassNode getDeclaringTypeFromDeclaration(ASTNode declaration, ClassNode resolvedTypeOfDeclaration) { ClassNode typeOfDeclaration; if (declaration instanceof FieldNode) { typeOfDeclaration = ((FieldNode) declaration).getDeclaringClass(); } else if (declaration instanceof MethodNode) { typeOfDeclaration = ((MethodNode) declaration).getDeclaringClass(); } else if (declaration instanceof PropertyNode) { typeOfDeclaration = ((PropertyNode) declaration).getDeclaringClass(); } else { typeOfDeclaration = VariableScope.OBJECT_CLASS_NODE; } // don't necessarily use the typeOfDeclaration. the resolvedTypeOfDeclaration includes the types of generics // so if the names are the same, then used the resolved version if (typeOfDeclaration.getName().equals(resolvedTypeOfDeclaration.getName())) { return resolvedTypeOfDeclaration; } else { return typeOfDeclaration; } } /** * Determines if the specified class expression is a class literal. */ protected boolean isClassLiteralExpression(ClassExpression classExpr, VariableScope scope) { ASTNode scopeNode = scope.getCurrentNode(); boolean probablyClassLiteral; if (scopeNode == null) { probablyClassLiteral = true; } else if (scopeNode instanceof ListExpression) { probablyClassLiteral = true; } else if (scopeNode instanceof MapEntryExpression) { probablyClassLiteral = true; } else if (scopeNode instanceof ConstantExpression) { probablyClassLiteral = true; } else if (scopeNode instanceof MethodCallExpression) { probablyClassLiteral = (classExpr != ((MethodCallExpression) scopeNode).getObjectExpression()); } else if (scopeNode instanceof PropertyExpression && classExpr.getEnd() > 0) { try { probablyClassLiteral = String.valueOf(unit.getContents(), classExpr.getStart(), classExpr.getLength()).endsWith(".class"); } catch (StringIndexOutOfBoundsException ex) { probablyClassLiteral = false; } } else { probablyClassLiteral = false; } return probablyClassLiteral; } /** * Determines if the specified method node is synthetic (i.e. generated or * implicit in some sense). */ protected static boolean isSynthetic(MethodNode method) { // TODO: What about 'method.getDeclaringClass().equals(ClassHelper.GROOVY_OBJECT_TYPE)'? return method.isSynthetic() || method.getDeclaringClass().equals(ClassHelper.CLOSURE_TYPE) || (method instanceof JDTMethodNode && ((JDTMethodNode) method).getJdtBinding() instanceof LazilyResolvedMethodBinding); } /** * Determines if the given argument types are compatible with the declaration parameters. * * @return {@link Boolean#TRUE true} for exact match, {@code null} for fuzzy match, and {@link Boolean#FALSE false} for not a match */ protected static Boolean isTypeCompatible(List<ClassNode> arguments, Parameter[] parameters) { // TODO: Add handling for variadic methods/constructors. // TODO: Can anything be learned from org.codehaus.groovy.ast.ClassNode.tryFindPossibleMethod(String, Expression)? Boolean result = Boolean.TRUE; for (int i = 0, n = parameters.length; i < n; i += 1) { ClassNode parameter = parameters[i].getType(), argument = arguments.get(i); // test parameter and argument for exact and fuzzy match Boolean partialResult = isTypeCompatible(argument, parameter); if (partialResult == null) { result = null; // fuzzy } else if (!partialResult) { result = Boolean.FALSE; break; } } return result; } // TODO: How much of this could/should be moved to GroovyUtils.isAssignable? protected static Boolean isTypeCompatible(ClassNode source, ClassNode target) { Boolean result = Boolean.TRUE; if (!target.equals(source) && !(source == VariableScope.NULL_TYPE && !target.isPrimitive()) && !(source.equals(ClassHelper.CLOSURE_TYPE) && ClassHelper.isSAMType(target))) { result = !GroovyUtils.isAssignable(source, target) ? Boolean.FALSE : null; // not an exact match } return result; } }