/* * 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.List; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.reflection.ParameterTypes; import org.codehaus.groovy.runtime.MetaClassHelper; import org.codehaus.jdt.groovy.model.GroovyCompilationUnit; import org.eclipse.jdt.groovy.core.util.GroovyUtils; import org.eclipse.jdt.groovy.search.TypeLookupResult.TypeConfidence; /** * Looks up the type of an expression in the currently applicable categories. * Note: DefaultGroovyMethods are always considered to be an applicable category. */ public class CategoryTypeLookup implements ITypeLookup { public TypeLookupResult lookupType(Expression node, VariableScope scope, ClassNode objectExpressionType) { if (node instanceof VariableExpression || (node instanceof ConstantExpression && ClassHelper.STRING_TYPE.equals(node.getType()) && node.getLength() <= node.getText().length())) { String simpleName = node.getText(); ClassNode expectedType = objectExpressionType; if (expectedType == null) expectedType = scope.getDelegateOrThis(); ClassNode normalizedType = GroovyUtils.getWrapperTypeIfPrimitive(expectedType); // List<MethodNode> candidates = new ArrayList<MethodNode>(); for (ClassNode category : scope.getCategoryNames()) { for (MethodNode method : category.getMethods(simpleName)) { if (isCompatibleCategoryMethod(method, normalizedType)) { candidates.add(method); } } String getterName = AccessorSupport.GETTER.createAccessorName(simpleName); if (getterName != null) { for (MethodNode method : category.getMethods(getterName)) { if (AccessorSupport.findAccessorKind(method, true) == AccessorSupport.GETTER && isCompatibleCategoryMethod(method, normalizedType)) { candidates.add(method); } } } String setterName = AccessorSupport.SETTER.createAccessorName(simpleName); if (setterName != null) { for (MethodNode method : category.getMethods(setterName)) { if (AccessorSupport.findAccessorKind(method, true) == AccessorSupport.SETTER && isCompatibleCategoryMethod(method, normalizedType)) { candidates.add(method); } } } } if (!candidates.isEmpty()) { int args = 1 + scope.getMethodCallNumberOfArguments(); List<ClassNode> argumentTypes = new ArrayList<ClassNode>(args); argumentTypes.add(normalizedType); // lhs of dot or delegate type if (args > 1) argumentTypes.addAll(scope.getMethodCallArgumentTypes()); MethodNode method = selectBestMatch(candidates, argumentTypes, scope); TypeLookupResult result = new TypeLookupResult(method.getReturnType(), method.getDeclaringClass(), method, isDefaultGroovyMethod(method) ? TypeConfidence.LOOSELY_INFERRED : TypeConfidence.INFERRED, scope); result.isGroovy = true; // enable semantic highlighting as Groovy method return result; } } return null; } protected boolean isCompatibleCategoryMethod(MethodNode method, ClassNode firstArgumentType) { if (method.isStatic()) { Parameter[] paramters = method.getParameters(); if (paramters != null && paramters.length > 0 && SimpleTypeLookup.isTypeCompatible(firstArgumentType, paramters[0].getType()) != Boolean.FALSE) { return true; } } return false; } protected boolean isDefaultGroovyMethod(MethodNode method) { return VariableScope.ALL_DEFAULT_CATEGORIES.contains(method.getDeclaringClass()); } /** * Selects the candidate that most closely matches the method call arguments. */ protected MethodNode selectBestMatch(List<MethodNode> candidates, List<ClassNode> argumentTypes, VariableScope scope) { MethodNode method = null; for (MethodNode candidate : candidates) { if (argumentTypes.size() == candidate.getParameters().length) { Boolean compatible = SimpleTypeLookup.isTypeCompatible(argumentTypes, candidate.getParameters()); if (compatible == Boolean.TRUE) { // exact match method = candidate; break; } else if (compatible != Boolean.FALSE) { // fuzzy match if (method != null) { long d1 = calculateParameterDistance(argumentTypes, method.getParameters()); long d2 = calculateParameterDistance(argumentTypes, candidate.getParameters()); if (d1 <= d2) continue; // stick with current selection } method = candidate; } else if (method == null) { method = candidate; // at least arguments line up with parameters } } } return method != null ? method : candidates.get(0); } private static long calculateParameterDistance(List<ClassNode> arguments, Parameter[] parameters) { try { // weight self type higher to prevent considering getAt(Map, Object) // and getAt(Object, String) equally for the arguments (Map, String) int n = 1 + arguments.size(); Class<?>[] args = new Class[n]; for (int i = 1; i < n; i += 1) { args[i] = arguments.get(i - 1).getTypeClass(); } args[0] = args[1]; // repeat the self type for effect n = 1 + parameters.length; Class<?>[] prms = new Class[n]; for (int i = 1; i < n; i += 1) { prms[i] = parameters[i - 1].getType().getTypeClass(); } prms[0] = prms[1]; // repeat the self type for effect // TODO: This can fail in a lot of cases; is there a better way to call it? return MetaClassHelper.calculateParameterDistance(args, new ParameterTypes(prms)); } catch (Throwable t) { return Long.MAX_VALUE; } } //-------------------------------------------------------------------------- public TypeLookupResult lookupType(AnnotationNode node, VariableScope scope) { return null; } public TypeLookupResult lookupType(ImportNode node, VariableScope scope) { return null; } public TypeLookupResult lookupType(ClassNode node, VariableScope scope) { return null; } public TypeLookupResult lookupType(FieldNode node, VariableScope scope) { return null; } public TypeLookupResult lookupType(MethodNode node, VariableScope scope) { return null; } public TypeLookupResult lookupType(Parameter node, VariableScope scope) { return null; } public void initialize(GroovyCompilationUnit unit, VariableScope topLevelScope) { // do nothing } }