/*
* Copyright 2003-2010 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.codehaus.groovy.transform.stc;
import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Boolean_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type;
import static org.codehaus.groovy.ast.ClassHelper.Character_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Enum_Type;
import static org.codehaus.groovy.ast.ClassHelper.GROOVY_OBJECT_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.GSTRING_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.char_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
import static org.codehaus.groovy.ast.ClassHelper.isNumberType;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE;
import static org.codehaus.groovy.syntax.Types.ASSIGN;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL;
import static org.codehaus.groovy.syntax.Types.BITWISE_OR;
import static org.codehaus.groovy.syntax.Types.BITWISE_OR_EQUAL;
import static org.codehaus.groovy.syntax.Types.BITWISE_XOR;
import static org.codehaus.groovy.syntax.Types.BITWISE_XOR_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN;
import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN;
import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_TO;
import static org.codehaus.groovy.syntax.Types.DIVIDE;
import static org.codehaus.groovy.syntax.Types.DIVIDE_EQUAL;
import static org.codehaus.groovy.syntax.Types.INTDIV;
import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL;
import static org.codehaus.groovy.syntax.Types.KEYWORD_IN;
import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF;
import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT;
import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT_EQUAL;
import static org.codehaus.groovy.syntax.Types.LEFT_SQUARE_BRACKET;
import static org.codehaus.groovy.syntax.Types.LOGICAL_AND;
import static org.codehaus.groovy.syntax.Types.LOGICAL_AND_EQUAL;
import static org.codehaus.groovy.syntax.Types.LOGICAL_OR;
import static org.codehaus.groovy.syntax.Types.LOGICAL_OR_EQUAL;
import static org.codehaus.groovy.syntax.Types.MATCH_REGEX;
import static org.codehaus.groovy.syntax.Types.MINUS;
import static org.codehaus.groovy.syntax.Types.MINUS_EQUAL;
import static org.codehaus.groovy.syntax.Types.MOD;
import static org.codehaus.groovy.syntax.Types.MOD_EQUAL;
import static org.codehaus.groovy.syntax.Types.MULTIPLY;
import static org.codehaus.groovy.syntax.Types.MULTIPLY_EQUAL;
import static org.codehaus.groovy.syntax.Types.PLUS;
import static org.codehaus.groovy.syntax.Types.PLUS_EQUAL;
import static org.codehaus.groovy.syntax.Types.POWER;
import static org.codehaus.groovy.syntax.Types.POWER_EQUAL;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_EQUAL;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL;
import groovy.lang.GroovySystem;
import groovy.lang.MetaClassRegistry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
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.ast.tools.GenericsUtils;
import org.codehaus.groovy.ast.tools.WideningCategories;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.DefaultGroovyStaticMethods;
import org.codehaus.groovy.runtime.m12n.ExtensionModule;
import org.codehaus.groovy.runtime.m12n.ExtensionModuleRegistry;
import org.codehaus.groovy.runtime.m12n.MetaInfExtensionModule;
import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl;
/**
* Static support methods for {@link StaticTypeCheckingVisitor}.
*/
public abstract class StaticTypeCheckingSupport {
final static ClassNode Collection_TYPE = makeWithoutCaching(Collection.class);
final static ClassNode Deprecated_TYPE = makeWithoutCaching(Deprecated.class);
final static ClassNode Matcher_TYPE = makeWithoutCaching(Matcher.class);
final static ClassNode ArrayList_TYPE = makeWithoutCaching(ArrayList.class);
final static ExtensionMethodCache EXTENSION_METHOD_CACHE = new ExtensionMethodCache();
final static Map<ClassNode, Integer> NUMBER_TYPES;
static {
final Map<ClassNode, Integer> types = new HashMap<ClassNode, Integer>(16);
types.put(ClassHelper.byte_TYPE, 0);
types.put(ClassHelper.Byte_TYPE, 0);
types.put(ClassHelper.short_TYPE, 1);
types.put(ClassHelper.Short_TYPE, 1);
types.put(ClassHelper.int_TYPE, 2);
types.put(ClassHelper.Integer_TYPE, 2);
types.put(ClassHelper.Long_TYPE, 3);
types.put(ClassHelper.long_TYPE, 3);
types.put(ClassHelper.float_TYPE, 4);
types.put(ClassHelper.Float_TYPE, 4);
types.put(ClassHelper.double_TYPE, 5);
types.put(ClassHelper.Double_TYPE, 5);
NUMBER_TYPES = Collections.unmodifiableMap(types);
}
final static ClassNode GSTRING_STRING_CLASSNODE = WideningCategories.lowestUpperBound(
ClassHelper.STRING_TYPE,
ClassHelper.GSTRING_TYPE
);
/**
* This is for internal use only. When an argument method is null, we cannot determine its type, so
* we use this one as a wildcard.
*/
final static ClassNode UNKNOWN_PARAMETER_TYPE = ClassHelper.make("<unknown parameter type>");
/**
* This comparator is used when we return the list of methods from DGM which name correspond to a given
* name. As we also lookup for DGM methods of superclasses or interfaces, it may be possible to find
* two methods which have the same name and the same arguments. In that case, we should not add the method
* from superclass or interface otherwise the system won't be able to select the correct method, resulting
* in an ambiguous method selection for similar methods.
*/
private static final Comparator<MethodNode> DGM_METHOD_NODE_COMPARATOR = new Comparator<MethodNode>() {
public int compare(final MethodNode o1, final MethodNode o2) {
if (o1.getName().equals(o2.getName())) {
Parameter[] o1ps = o1.getParameters();
Parameter[] o2ps = o2.getParameters();
if (o1ps.length == o2ps.length) {
boolean allEqual = true;
for (int i = 0; i < o1ps.length && allEqual; i++) {
allEqual = o1ps[i].getType().equals(o2ps[i].getType());
}
if (allEqual) {
if (o1 instanceof ExtensionMethodNode && o2 instanceof ExtensionMethodNode) {
return compare(((ExtensionMethodNode) o1).getExtensionMethodNode(), ((ExtensionMethodNode) o2).getExtensionMethodNode());
}
return 0;
}
} else {
return o1ps.length - o2ps.length;
}
}
return 1;
}
};
/**
* Returns true for expressions of the form x[...]
* @param expression an expression
* @return true for array access expressions
*/
static boolean isArrayAccessExpression(Expression expression) {
return expression instanceof BinaryExpression && isArrayOp(((BinaryExpression) expression).getOperation().getType());
}
/**
* Called on method call checks in order to determine if a method call corresponds to the
* idiomatic o.with { ... } structure
* @param name name of the method called
* @param callArguments arguments of the method
* @return true if the name is "with" and arguments consist of a single closure
*/
public static boolean isWithCall(final String name, final Expression callArguments) {
boolean isWithCall = "with".equals(name) && callArguments instanceof ArgumentListExpression;
if (isWithCall) {
ArgumentListExpression argList = (ArgumentListExpression) callArguments;
List<Expression> expressions = argList.getExpressions();
isWithCall = expressions.size() == 1 && expressions.get(0) instanceof ClosureExpression;
}
return isWithCall;
}
/**
* Given a variable expression, returns the ultimately accessed variable.
* @param ve a variable expression
* @return the target variable
*/
static Variable findTargetVariable(VariableExpression ve) {
final Variable accessedVariable = ve.getAccessedVariable() != null ? ve.getAccessedVariable() : ve;
if (accessedVariable != ve) {
if (accessedVariable instanceof VariableExpression)
return findTargetVariable((VariableExpression) accessedVariable);
}
return accessedVariable;
}
static Set<MethodNode> findDGMMethodsForClassNode(ClassNode clazz, String name) {
TreeSet<MethodNode> accumulator = new TreeSet<MethodNode>(DGM_METHOD_NODE_COMPARATOR);
findDGMMethodsForClassNode(clazz, name, accumulator);
return accumulator;
}
static void findDGMMethodsForClassNode(ClassNode clazz, String name, TreeSet<MethodNode> accumulator) {
List<MethodNode> fromDGM = EXTENSION_METHOD_CACHE.getExtensionMethods().get(clazz.getName());
if (fromDGM != null) {
for (MethodNode node : fromDGM) {
if (node.getName().equals(name)) accumulator.add(node);
}
}
for (ClassNode node : clazz.getInterfaces()) {
findDGMMethodsForClassNode(node, name, accumulator);
}
if (clazz.isArray()) {
ClassNode componentClass = clazz.getComponentType();
if (!componentClass.equals(OBJECT_TYPE)) {
if (componentClass.isInterface() || componentClass.getSuperClass()==null) {
findDGMMethodsForClassNode(OBJECT_TYPE.makeArray(), name, accumulator);
} else {
findDGMMethodsForClassNode(componentClass.getSuperClass().makeArray(), name, accumulator);
}
}
}
if (clazz.getSuperClass() != null) {
findDGMMethodsForClassNode(clazz.getSuperClass(), name, accumulator);
} else if (!clazz.equals(ClassHelper.OBJECT_TYPE)) {
findDGMMethodsForClassNode(ClassHelper.OBJECT_TYPE, name, accumulator);
}
}
/**
* Checks that arguments and parameter types match.
* @param params method parameters
* @param args type arguments
* @return -1 if arguments do not match, 0 if arguments are of the exact type and >0 when one or more argument is
* not of the exact type but still match
*/
public static int allParametersAndArgumentsMatch(Parameter[] params, ClassNode[] args) {
if (params==null) {
params = Parameter.EMPTY_ARRAY;
}
int dist = 0;
if (args.length<params.length) return -1;
// we already know the lengths are equal
for (int i = 0; i < params.length; i++) {
ClassNode paramType = params[i].getType();
ClassNode argType = args[i];
if (!isAssignableTo(argType, paramType)) return -1;
else {
if (!paramType.equals(argType)) dist+=getDistance(argType, paramType);
}
}
return dist;
}
/**
* Checks that arguments and parameter types match, expecting that the number of parameters is strictly greater
* than the number of arguments, allowing possible inclusion of default parameters.
* @param params method parameters
* @param args type arguments
* @return -1 if arguments do not match, 0 if arguments are of the exact type and >0 when one or more argument is
* not of the exact type but still match
*/
static int allParametersAndArgumentsMatchWithDefaultParams(Parameter[] params, ClassNode[] args) {
int dist = 0;
ClassNode ptype = null;
// we already know the lengths are equal
for (int i = 0, j=0; i < params.length; i++) {
Parameter param = params[i];
ClassNode paramType = param.getType();
ClassNode arg = j>=args.length?null:args[j];
if (arg==null || !isAssignableTo(arg, paramType)){
if (!param.hasInitialExpression() && (ptype==null || !ptype.equals(paramType))) {
return -1; // no default value
}
// a default value exists, we can skip this param
ptype = null;
} else {
j++;
if (!paramType.equals(arg)) dist+=getDistance(arg, paramType);
if (param.hasInitialExpression()) {
ptype = arg;
} else {
ptype = null;
}
}
}
return dist;
}
/**
* Checks that excess arguments match the vararg signature parameter.
* @param params
* @param args
* @return -1 if no match, 0 if all arguments matches the vararg type and >0 if one or more vararg argument is
* assignable to the vararg type, but still not an exact match
*/
static int excessArgumentsMatchesVargsParameter(Parameter[] params, ClassNode[] args) {
// we already know parameter length is bigger zero and last is a vargs
// the excess arguments are all put in an array for the vargs call
// so check against the component type
int dist = 0;
ClassNode vargsBase = params[params.length - 1].getType().getComponentType();
for (int i = params.length; i < args.length; i++) {
if (!isAssignableTo(args[i],vargsBase)) return -1;
else if (!args[i].equals(vargsBase)) dist+=getDistance(args[i], vargsBase);
}
return dist;
}
/**
* Checks if the last argument matches the vararg type.
* @param params
* @param args
* @return -1 if no match, 0 if the last argument is exactly the vararg type and 1 if of an assignable type
*/
static int lastArgMatchesVarg(Parameter[] params, ClassNode... args) {
if (!isVargs(params)) return -1;
// case length ==0 handled already
// we have now two cases,
// the argument is wrapped in the vargs array or
// the argument is an array that can be used for the vargs part directly
// we test only the wrapping part, since the non wrapping is done already
ClassNode ptype = params[params.length - 1].getType().getComponentType();
ClassNode arg = args[args.length - 1];
if (isNumberType(ptype) && isNumberType(arg) && !ptype.equals(arg)) return -1;
return isAssignableTo(arg, ptype)?getDistance(arg,ptype):-1;
}
/**
* Checks if a class node is assignable to another. This is used for example in
* assignment checks where you want to verify that the assignment is valid.
* @param type
* @param toBeAssignedTo
* @return
*/
static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) {
if (UNKNOWN_PARAMETER_TYPE==type) return true;
if (toBeAssignedTo.redirect() == STRING_TYPE && type.redirect() == GSTRING_TYPE) {
return true;
}
if (isPrimitiveType(toBeAssignedTo)) toBeAssignedTo = getWrapper(toBeAssignedTo);
if (isPrimitiveType(type)) type = getWrapper(type);
if (ClassHelper.Double_TYPE==toBeAssignedTo) {
return type.isDerivedFrom(Number_TYPE);
}
if (ClassHelper.Float_TYPE==toBeAssignedTo) {
return type.isDerivedFrom(Number_TYPE) && ClassHelper.Double_TYPE!=type.redirect();
}
if (ClassHelper.Long_TYPE==toBeAssignedTo) {
return type.isDerivedFrom(Number_TYPE)
&& ClassHelper.Double_TYPE!=type.redirect()
&& ClassHelper.Float_TYPE!=type.redirect();
}
if (ClassHelper.Integer_TYPE==toBeAssignedTo) {
return type.isDerivedFrom(Number_TYPE)
&& ClassHelper.Double_TYPE!=type.redirect()
&& ClassHelper.Float_TYPE!=type.redirect()
&& ClassHelper.Long_TYPE!=type.redirect();
}
if (ClassHelper.Short_TYPE==toBeAssignedTo) {
return type.isDerivedFrom(Number_TYPE)
&& ClassHelper.Double_TYPE!=type.redirect()
&& ClassHelper.Float_TYPE!=type.redirect()
&& ClassHelper.Long_TYPE!=type.redirect()
&& ClassHelper.Integer_TYPE!=type.redirect();
}
if (ClassHelper.Byte_TYPE==toBeAssignedTo) {
return type.redirect() == ClassHelper.Byte_TYPE;
}
if (type.isArray() && toBeAssignedTo.isArray()) {
return isAssignableTo(type.getComponentType(),toBeAssignedTo.getComponentType());
}
if (type.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(toBeAssignedTo)) {
return true;
}
if (toBeAssignedTo.isDerivedFrom(GSTRING_TYPE) && STRING_TYPE.equals(type)) {
return true;
}
if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) {
if (OBJECT_TYPE.equals(toBeAssignedTo)) return true;
if (toBeAssignedTo.isUsingGenerics()) {
// perform additional check on generics
// ? extends toBeAssignedTo
GenericsType gt = GenericsUtils.buildWildcardType(toBeAssignedTo);
return gt.isCompatibleWith(type);
}
return true;
} else {
return false;
}
}
static boolean isVargs(Parameter[] params) {
if (params.length == 0) return false;
if (params[params.length - 1].getType().isArray()) return true;
return false;
}
static boolean isCompareToBoolean(int op) {
return op == COMPARE_GREATER_THAN ||
op == COMPARE_GREATER_THAN_EQUAL ||
op == COMPARE_LESS_THAN ||
op == COMPARE_LESS_THAN_EQUAL;
}
static boolean isArrayOp(int op) {
return op == LEFT_SQUARE_BRACKET;
}
static boolean isBoolIntrinsicOp(int op) {
return op == LOGICAL_AND || op == LOGICAL_OR ||
op == MATCH_REGEX || op == KEYWORD_INSTANCEOF;
}
static boolean isPowerOperator(int op) {
return op == POWER || op == POWER_EQUAL;
}
static String getOperationName(int op) {
switch (op) {
case COMPARE_EQUAL:
case COMPARE_NOT_EQUAL:
// this is only correct in this context here, normally
// we would have to compile against compareTo if available
// but since we don't compile here, this one is enough
return "equals";
case COMPARE_TO:
case COMPARE_GREATER_THAN:
case COMPARE_GREATER_THAN_EQUAL:
case COMPARE_LESS_THAN:
case COMPARE_LESS_THAN_EQUAL:
return "compareTo";
case BITWISE_AND:
case BITWISE_AND_EQUAL:
return "and";
case BITWISE_OR:
case BITWISE_OR_EQUAL:
return "or";
case BITWISE_XOR:
case BITWISE_XOR_EQUAL:
return "xor";
case PLUS:
case PLUS_EQUAL:
return "plus";
case MINUS:
case MINUS_EQUAL:
return "minus";
case MULTIPLY:
case MULTIPLY_EQUAL:
return "multiply";
case DIVIDE:
case DIVIDE_EQUAL:
return "div";
case INTDIV:
case INTDIV_EQUAL:
return "intdiv";
case MOD:
case MOD_EQUAL:
return "mod";
case POWER:
case POWER_EQUAL:
return "power";
case LEFT_SHIFT:
case LEFT_SHIFT_EQUAL:
return "leftShift";
case RIGHT_SHIFT:
case RIGHT_SHIFT_EQUAL:
return "rightShift";
case RIGHT_SHIFT_UNSIGNED:
case RIGHT_SHIFT_UNSIGNED_EQUAL:
return "rightShiftUnsigned";
case KEYWORD_IN:
return "isCase";
default:
return null;
}
}
static boolean isShiftOperation(String name) {
return "leftShift".equals(name) || "rightShift".equals(name) || "rightShiftUnsigned".equals(name);
}
/**
* Returns true for operations that are of the class, that given a common type class for left and right, the
* operation "left op right" will have a result in the same type class In Groovy on numbers that is +,-,* as well as
* their variants with equals.
*/
static boolean isOperationInGroup(int op) {
switch (op) {
case PLUS:
case PLUS_EQUAL:
case MINUS:
case MINUS_EQUAL:
case MULTIPLY:
case MULTIPLY_EQUAL:
return true;
default:
return false;
}
}
static boolean isBitOperator(int op) {
switch (op) {
case BITWISE_OR_EQUAL:
case BITWISE_OR:
case BITWISE_AND_EQUAL:
case BITWISE_AND:
case BITWISE_XOR_EQUAL:
case BITWISE_XOR:
return true;
default:
return false;
}
}
public static boolean isAssignment(int op) {
switch (op) {
case ASSIGN:
case LOGICAL_OR_EQUAL:
case LOGICAL_AND_EQUAL:
case PLUS_EQUAL:
case MINUS_EQUAL:
case MULTIPLY_EQUAL:
case DIVIDE_EQUAL:
case INTDIV_EQUAL:
case MOD_EQUAL:
case POWER_EQUAL:
case LEFT_SHIFT_EQUAL:
case RIGHT_SHIFT_EQUAL:
case RIGHT_SHIFT_UNSIGNED_EQUAL:
case BITWISE_OR_EQUAL:
case BITWISE_AND_EQUAL:
case BITWISE_XOR_EQUAL:
return true;
default:
return false;
}
}
/**
* Returns true or false depending on whether the right classnode can be assigned to the left classnode. This method
* should not add errors by itself: we let the caller decide what to do if an incompatible assignment is found.
*
* @param left the class to be assigned to
* @param right the assignee class
* @return false if types are incompatible
*/
public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right) {
return checkCompatibleAssignmentTypes(left, right, null);
}
public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right, Expression rightExpression) {
ClassNode leftRedirect = left.redirect();
ClassNode rightRedirect = right.redirect();
if (leftRedirect==rightRedirect) return true;
if (leftRedirect.isArray() && rightRedirect.isArray()) {
return checkCompatibleAssignmentTypes(leftRedirect.getComponentType(), rightRedirect.getComponentType(), rightExpression);
}
if (right==VOID_TYPE||right==void_WRAPPER_TYPE) {
return left==VOID_TYPE||left==void_WRAPPER_TYPE;
}
if ((isNumberType(rightRedirect)||WideningCategories.isNumberCategory(rightRedirect))) {
if (BigDecimal_TYPE==leftRedirect) {
// any number can be assigned to a big decimal
return true;
}
if (BigInteger_TYPE==leftRedirect) {
return WideningCategories.isBigIntCategory(getUnwrapper(rightRedirect));
}
}
// if rightExpression is null and leftExpression is not a primitive type, it's ok
boolean rightExpressionIsNull = rightExpression instanceof ConstantExpression && ((ConstantExpression) rightExpression).getValue()==null;
if (rightExpressionIsNull && !isPrimitiveType(left)) {
return true;
}
// on an assignment everything that can be done by a GroovyCast is allowed
// anything can be assigned to an Object, String, boolean, Boolean
// or Class typed variable
if (isWildcardLeftHandSide(leftRedirect)) return true;
// GRECLIPSE: start
/*old{
if (leftRedirect == OBJECT_TYPE ||
leftRedirect == STRING_TYPE ||
leftRedirect == boolean_TYPE ||
leftRedirect == Boolean_TYPE ||
leftRedirect == CLASS_Type) {
return true;
}
}*///new:
/*
if (leftRedirect.equals(OBJECT_TYPE) ||
leftRedirect.equals(STRING_TYPE) ||
leftRedirect.equals(boolean_TYPE) ||
leftRedirect.equals(Boolean_TYPE) ||
leftRedirect.equals(CLASS_Type)) {
return true;
}
*/
// GRECLIPSE: end
// char as left expression
if (leftRedirect == char_TYPE && rightRedirect==STRING_TYPE) {
if (rightExpression!=null && rightExpression instanceof ConstantExpression) {
String value = rightExpression.getText();
return value.length()==1;
}
}
if (leftRedirect == Character_TYPE && (rightRedirect==STRING_TYPE||rightExpressionIsNull)) {
return rightExpressionIsNull || (rightExpression instanceof ConstantExpression && rightExpression.getText().length()==1);
}
// if left is Enum and right is String or GString we do valueOf
if (leftRedirect.isDerivedFrom(Enum_Type) &&
(rightRedirect == GSTRING_TYPE || rightRedirect == STRING_TYPE)) {
return true;
}
// if right is array, map or collection we try invoking the
// constructor
if (rightRedirect.implementsInterface(MAP_TYPE) ||
rightRedirect.implementsInterface(Collection_TYPE) ||
rightRedirect.equals(MAP_TYPE) ||
rightRedirect.equals(Collection_TYPE) ||
rightRedirect.isArray()) {
//TODO: in case of the array we could maybe make a partial check
if (leftRedirect.isArray() && rightRedirect.isArray()) {
return checkCompatibleAssignmentTypes(leftRedirect.getComponentType(), rightRedirect.getComponentType());
} else if (rightRedirect.isArray() && !leftRedirect.isArray()) {
return false;
}
return true;
}
// simple check on being subclass
if (right.isDerivedFrom(left) || (left.isInterface() && right.implementsInterface(left))) return true;
// if left and right are primitives or numbers allow
if (isPrimitiveType(leftRedirect) && isPrimitiveType(rightRedirect)) return true;
if (isNumberType(leftRedirect) && isNumberType(rightRedirect)) return true;
// left is a float/double and right is a BigDecimal
if (WideningCategories.isFloatingCategory(leftRedirect) && BigDecimal_TYPE.equals(rightRedirect)) {
return true;
}
if (GROOVY_OBJECT_TYPE.equals(leftRedirect) && isBeingCompiled(right)) {
return true;
}
return false;
}
/**
* Tells if a class is one of the "accept all" classes as the left hand side of an
* assignment.
* @param node the classnode to test
* @return true if it's an Object, String, boolean, Boolean or Class.
*/
public static boolean isWildcardLeftHandSide(final ClassNode node) {
/* this is what it was in 2.0.6:
if (OBJECT_TYPE.equals(node) ||
STRING_TYPE.equals(node) ||
boolean_TYPE.equals(node) ||
Boolean_TYPE.equals(node) ||
CLASS_Type.equals(node)) {
return true;
}
*/
// GRECLIPSE: start
/*old{
if (leftRedirect == OBJECT_TYPE ||
leftRedirect == STRING_TYPE ||
leftRedirect == boolean_TYPE ||
leftRedirect == Boolean_TYPE ||
leftRedirect == CLASS_Type) {
return true;
}
}*///new:
if (node.equals(OBJECT_TYPE) ||
node.equals(STRING_TYPE) ||
node.equals(boolean_TYPE) ||
node.equals(Boolean_TYPE) ||
node.equals(CLASS_Type)) {
return true;
}
// GRECLIPSE: end
return false;
}
public static boolean isBeingCompiled(ClassNode node) {
return node.getCompileUnit() != null;
}
static boolean checkPossibleLooseOfPrecision(ClassNode left, ClassNode right, Expression rightExpr) {
if (left == right || left.equals(right)) return false; // identical types
int leftIndex = NUMBER_TYPES.get(left);
int rightIndex = NUMBER_TYPES.get(right);
if (leftIndex >= rightIndex) return false;
// here we must check if the right number is short enough to fit in the left type
if (rightExpr instanceof ConstantExpression) {
Object value = ((ConstantExpression) rightExpr).getValue();
if (!(value instanceof Number)) return true;
Number number = (Number) value;
switch (leftIndex) {
case 0: { // byte
byte val = number.byteValue();
if (number instanceof Short) {
return !Short.valueOf(val).equals(number);
}
if (number instanceof Integer) {
return !Integer.valueOf(val).equals(number);
}
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 1: { // short
short val = number.shortValue();
if (number instanceof Integer) {
return !Integer.valueOf(val).equals(number);
}
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 2: { // integer
int val = number.intValue();
if (number instanceof Long) {
return !Long.valueOf(val).equals(number);
}
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 3: { // long
long val = number.longValue();
if (number instanceof Float) {
return !Float.valueOf(val).equals(number);
}
return !Double.valueOf(val).equals(number);
}
case 4: { // float
float val = number.floatValue();
return !Double.valueOf(val).equals(number);
}
default: // double
return false; // no possible loose here
}
}
return true; // possible loose of precision
}
static String toMethodParametersString(String methodName, ClassNode... parameters) {
StringBuilder sb = new StringBuilder();
sb.append(methodName).append("(");
if (parameters != null) {
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
final ClassNode parameter = parameters[i];
sb.append(prettyPrintType(parameter));
if (i < parametersLength - 1) sb.append(", ");
}
}
sb.append(")");
return sb.toString();
}
static String prettyPrintType(ClassNode type) {
if (type.isArray()) {
return prettyPrintType(type.getComponentType())+"[]";
}
return type.toString(false);
}
public static boolean implementsInterfaceOrIsSubclassOf(ClassNode type, ClassNode superOrInterface) {
boolean result = type.equals(superOrInterface)
|| type.isDerivedFrom(superOrInterface)
|| type.implementsInterface(superOrInterface)
|| type == UNKNOWN_PARAMETER_TYPE;
if (result) {
return true;
}
if (superOrInterface instanceof WideningCategories.LowestUpperBoundClassNode) {
WideningCategories.LowestUpperBoundClassNode cn = (WideningCategories.LowestUpperBoundClassNode) superOrInterface;
result = implementsInterfaceOrIsSubclassOf(type, cn.getSuperClass());
if (result) {
for (ClassNode interfaceNode : cn.getInterfaces()) {
result = type.implementsInterface(interfaceNode);
if (!result) break;
}
}
if (result) return true;
} else if (superOrInterface instanceof UnionTypeClassNode) {
UnionTypeClassNode union = (UnionTypeClassNode) superOrInterface;
for (ClassNode delegate : union.getDelegates()) {
if (implementsInterfaceOrIsSubclassOf(type, delegate)) return true;
}
}
if (type.isArray() && superOrInterface.isArray()) {
return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType());
}
if (GROOVY_OBJECT_TYPE.equals(superOrInterface) && !type.isInterface() && isBeingCompiled(type)) {
return true;
}
return false;
}
static int getPrimitiveDistance(ClassNode primA, ClassNode primB) {
return Math.abs(NUMBER_TYPES.get(primA)-NUMBER_TYPES.get(primB));
}
static int getDistance(final ClassNode receiver, final ClassNode compare) {
int dist = 0;
ClassNode unwrapReceiver = ClassHelper.getUnwrapper(receiver);
ClassNode unwrapCompare = ClassHelper.getUnwrapper(compare);
if (ClassHelper.isPrimitiveType(unwrapReceiver)
&& ClassHelper.isPrimitiveType(unwrapCompare)
&& unwrapReceiver!=unwrapCompare) {
dist = getPrimitiveDistance(unwrapReceiver, unwrapCompare);
}
if (isPrimitiveType(receiver) && !isPrimitiveType(compare)) {
dist = (dist+1)<<1;
}
if (unwrapCompare.equals(unwrapReceiver)) return dist;
if (receiver.isArray() && !compare.isArray()) {
// Object[] vs Object
dist += 256;
}
if (receiver == UNKNOWN_PARAMETER_TYPE) {
return dist;
}
ClassNode ref = receiver;
while (ref!=null) {
if (compare.equals(ref)) {
break;
}
if (compare.isInterface() && ref.implementsInterface(compare)) {
dist += getMaximumInterfaceDistance(ref, compare);
break;
}
ref = ref.getSuperClass();
if (ref == null) dist += 2;
dist = (dist+1)<<1;
}
return dist;
}
private static int getMaximumInterfaceDistance(ClassNode c, ClassNode interfaceClass) {
// -1 means a mismatch
if (c == null) return -1;
// 0 means a direct match
if (c.equals(interfaceClass)) return 0;
ClassNode[] interfaces = c.getInterfaces();
int max = -1;
for (ClassNode anInterface : interfaces) {
int sub = getMaximumInterfaceDistance(anInterface, interfaceClass);
// we need to keep the -1 to track the mismatch, a +1
// by any means could let it look like a direct match
// we want to add one, because there is an interface between
// the interface we search for and the interface we are in.
if (sub != -1) sub++;
// we are interested in the longest path only
max = Math.max(max, sub);
}
// we do not add one for super classes, only for interfaces
int superClassMax = getMaximumInterfaceDistance(c.getSuperClass(), interfaceClass);
return Math.max(max, superClassMax);
}
public static List<MethodNode> findDGMMethodsByNameAndArguments(final ClassNode receiver, final String name, final ClassNode[] args) {
return findDGMMethodsByNameAndArguments(receiver, name, args, new LinkedList<MethodNode>());
}
public static List<MethodNode> findDGMMethodsByNameAndArguments(final ClassNode receiver, final String name, final ClassNode[] args, final List<MethodNode> methods) {
final List<MethodNode> chosen;
methods.addAll(findDGMMethodsForClassNode(receiver, name));
chosen = chooseBestMethod(receiver, methods, args);
// specifically for DGM-like methods, we may have a generic type as the first argument of the DGM method
// for example: DGM#getAt(T[], int) or DGM#putAt(T[], int, U)
// in that case, we must verify that the chosen method match generic type information
Iterator<MethodNode> iterator = chosen.iterator();
while (iterator.hasNext()) {
ExtensionMethodNode emn = (ExtensionMethodNode) iterator.next();
MethodNode dgmMethod = emn.getExtensionMethodNode(); // this is the method from DGM
GenericsType[] methodGenericTypes = dgmMethod.getGenericsTypes();
if (methodGenericTypes !=null && methodGenericTypes.length>0) {
Parameter[] parameters = dgmMethod.getParameters();
ClassNode dgmOwnerType = parameters[0].getOriginType();
if (dgmOwnerType.isGenericsPlaceHolder() || dgmOwnerType.isArray() && dgmOwnerType.getComponentType().isGenericsPlaceHolder()) {
// first parameter of DGM method is a generic type or an array of generic type
ClassNode receiverBase = receiver.isArray() ? receiver.getComponentType() : receiver;
ClassNode receiverBaseRedirect = dgmOwnerType.isArray()?dgmOwnerType.getComponentType():dgmOwnerType;
boolean mismatch = false;
// ex: <T, U extends T> void putAt(T[], int, U)
for (int i = 1; i < parameters.length && !mismatch; i++) {
final int k = i - 1; // index of the actual parameter because of the extra receiver parameter in DGM
ClassNode type = parameters[i].getOriginType();
if (isUsingGenericsOrIsArrayUsingGenerics(type)) {
// in a DGM-like method, the first parameter is the receiver. Because of type erasure,
// it can only be T or T[]
String receiverPlaceholder = receiverBaseRedirect.getGenericsTypes()[0].getName();
ClassNode parameterBaseType = args[k].isArray() ? args[k].getComponentType() : args[k];
ClassNode parameterBaseTypeRedirect = type.isArray() ? type.getComponentType() : type;
GenericsType[] paramRedirectGenericsTypes = parameterBaseTypeRedirect.getGenericsTypes();
GenericsType[] paramGenericTypes = parameterBaseType.getGenericsTypes();
if (paramGenericTypes==null) {
paramGenericTypes = new GenericsType[paramRedirectGenericsTypes.length];
Arrays.fill(paramGenericTypes, new GenericsType(OBJECT_TYPE));
} else {
for (int j = 0; j < paramGenericTypes.length; j++) {
GenericsType paramGenericType = paramGenericTypes[j];
if (paramGenericType.isWildcard() || paramGenericType.isPlaceholder()) {
// this may happen if an argument has been used without specifying a generic type
// for example, foo(List) instead of foo(List<Object>)
paramGenericTypes[j] = new GenericsType(OBJECT_TYPE);
}
}
}
for (int j = 0, genericsTypesLength = paramRedirectGenericsTypes.length; j < genericsTypesLength && !mismatch; j++) {
final GenericsType gt = paramRedirectGenericsTypes[j];
if (gt.isPlaceholder()) {
List<GenericsType> fromMethodGenerics = new LinkedList<GenericsType>();
for (GenericsType methodGenericType : methodGenericTypes) {
if (methodGenericType.getName().equals(gt.getName())) {
fromMethodGenerics.add(methodGenericType);
break;
}
}
while (!fromMethodGenerics.isEmpty()) {
// type must either be T or a derived type from T (ex: U extends T)
GenericsType test = fromMethodGenerics.remove(0);
if (test.getName().equals(receiverPlaceholder)) {
if (!implementsInterfaceOrIsSubclassOf(getWrapper(args[k]), getWrapper(receiverBase))) {
mismatch = true;
break;
}
} else if (test.getUpperBounds()!=null) {
for (ClassNode classNode : test.getUpperBounds()) {
GenericsType[] genericsTypes = classNode.getGenericsTypes();
if (genericsTypes!=null) {
for (GenericsType genericsType : genericsTypes) {
if (genericsType.isPlaceholder()) {
for (GenericsType methodGenericType : methodGenericTypes) {
if (methodGenericType.getName().equals(genericsType.getName())) {
fromMethodGenerics.add(methodGenericType);
break;
}
}
}
}
}
}
}
}
}
}
if (mismatch) {
iterator.remove();
}
}
}
}
}
}
return chosen;
}
/**
* Given a list of candidate methods, returns the one which best matches the argument types
*
* @param receiver
* @param methods candidate methods
* @param args argument types
* @return the list of methods which best matches the argument types. It is still possible that multiple
* methods match the argument types.
*/
public static List<MethodNode> chooseBestMethod(final ClassNode receiver, Collection<MethodNode> methods, ClassNode... args) {
if (methods.isEmpty()) return Collections.emptyList();
List<MethodNode> bestChoices = new LinkedList<MethodNode>();
int bestDist = Integer.MAX_VALUE;
ClassNode actualReceiver;
Collection<MethodNode> choicesLeft = removeCovariants(methods);
for (MethodNode m : choicesLeft) {
final ClassNode declaringClass = m.getDeclaringClass();
actualReceiver = receiver!=null?receiver: declaringClass;
// todo : corner case
/*
class B extends A {}
Animal foo(A o) {...}
Person foo(B i){...}
B a = new B()
Person p = foo(b)
*/
Parameter[] params = parameterizeArguments(actualReceiver, m);
if (params.length == args.length) {
int allPMatch = allParametersAndArgumentsMatch(params, args);
boolean firstParamMatches = true;
// check first parameters
if (args.length > 0) {
Parameter[] firstParams = new Parameter[params.length - 1];
System.arraycopy(params, 0, firstParams, 0, firstParams.length);
firstParamMatches = allParametersAndArgumentsMatch(firstParams, args) >= 0;
}
int lastArgMatch = isVargs(params) && firstParamMatches?lastArgMatchesVarg(params, args):-1;
if (lastArgMatch>=0) {
lastArgMatch += 256-params.length; // ensure exact matches are preferred over vargs
}
int dist = allPMatch>=0?Math.max(allPMatch, lastArgMatch):lastArgMatch;
if (dist>=0 && !actualReceiver.equals(declaringClass)) dist+=getDistance(actualReceiver, declaringClass);
if (dist>=0 && dist<bestDist) {
bestChoices.clear();
bestChoices.add(m);
bestDist = dist;
} else if (dist>=0 && dist==bestDist) {
bestChoices.add(m);
}
} else if (isVargs(params)) {
boolean firstParamMatches = true;
// check first parameters
if (args.length > 0) {
Parameter[] firstParams = new Parameter[params.length - 1];
System.arraycopy(params, 0, firstParams, 0, firstParams.length);
firstParamMatches = allParametersAndArgumentsMatch(firstParams, args) >= 0;
}
if (firstParamMatches) {
// there are three case for vargs
// (1) varg part is left out
if (params.length == args.length + 1) {
if (bestDist > 1) {
bestChoices.clear();
bestChoices.add(m);
bestDist = 1;
}
} else {
// (2) last argument is put in the vargs array
// that case is handled above already
// (3) there is more than one argument for the vargs array
int dist = excessArgumentsMatchesVargsParameter(params, args);
if (dist >= 0 && !actualReceiver.equals(declaringClass)) dist+=getDistance(actualReceiver, declaringClass);
// varargs methods must not be preferred to methods without varargs
// for example :
// int sum(int x) should be preferred to int sum(int x, int... y)
dist+=256-params.length;
if (params.length < args.length && dist >= 0) {
if (dist >= 0 && dist < bestDist) {
bestChoices.clear();
bestChoices.add(m);
bestDist = dist;
} else if (dist >= 0 && dist == bestDist) {
bestChoices.add(m);
}
}
}
}
}
}
return bestChoices;
}
private static Collection<MethodNode> removeCovariants(Collection<MethodNode> collection) {
if (collection.size()<=1) return collection;
List<MethodNode> toBeRemoved = new LinkedList<MethodNode>();
List<MethodNode> list = new LinkedList<MethodNode>(new HashSet<MethodNode>(collection));
for (int i=0;i<list.size()-1;i++) {
MethodNode one = list.get(i);
if (toBeRemoved.contains(one)) continue;
for (int j=i+1;j<list.size();j++) {
MethodNode two = list.get(j);
if (toBeRemoved.contains(two)) continue;
if (one.getName().equals(two.getName()) && one.getDeclaringClass()==two.getDeclaringClass()) {
Parameter[] onePars = one.getParameters();
Parameter[] twoPars = two.getParameters();
if (onePars.length == twoPars.length) {
boolean sameTypes = true;
for (int k = 0; k < onePars.length; k++) {
Parameter onePar = onePars[k];
Parameter twoPar = twoPars[k];
if (!onePar.getType().equals(twoPar.getType())) {
sameTypes = false;
break;
}
}
if (sameTypes) {
ClassNode oneRT = one.getReturnType();
ClassNode twoRT = two.getReturnType();
if (oneRT.isDerivedFrom(twoRT) || oneRT.implementsInterface(twoRT)) {
toBeRemoved.add(two);
} else if (twoRT.isDerivedFrom(oneRT) || twoRT.implementsInterface(oneRT)) {
toBeRemoved.add(one);
}
} else {
// this is an imperfect solution to determining if two methods are
// equivalent, for example String#compareTo(Object) and String#compareTo(String)
// in that case, Java marks the Object version as synthetic
if (one.isSynthetic() && !two.isSynthetic()) {
toBeRemoved.add(one);
} else if (two.isSynthetic() && !one.isSynthetic()) {
toBeRemoved.add(two);
}
}
}
}
}
}
if (toBeRemoved.isEmpty()) return list;
List<MethodNode> result = new LinkedList<MethodNode>(list);
result.removeAll(toBeRemoved);
return result;
}
/**
* Given a receiver and a method node, parameterize the method arguments using
* available generic type information.
*
* @param receiver the class
* @param m the method
* @return the parameterized arguments
*/
public static Parameter[] parameterizeArguments(final ClassNode receiver, final MethodNode m) {
MethodNode mn = m;
ClassNode actualReceiver = receiver;
/*if (m instanceof ExtensionMethodNode) {
ExtensionMethodNode emn = (ExtensionMethodNode) m;
mn = emn.getExtensionMethodNode();
actualReceiver = emn.getDeclaringClass();
}*/
List<GenericsType> redirectTypes = new ArrayList<GenericsType>();
// if (mn.getGenericsTypes()!=null) Collections.addAll(redirectTypes,mn.getGenericsTypes());
if (actualReceiver.redirect().getGenericsTypes()!=null) {
Collections.addAll(redirectTypes,actualReceiver.redirect().getGenericsTypes());
}
if (redirectTypes.isEmpty()) {
return m.getParameters();
}
GenericsType[] redirectReceiverTypes = redirectTypes.toArray(new GenericsType[redirectTypes.size()]);
Parameter[] methodParameters = mn.getParameters();
Parameter[] params = new Parameter[methodParameters.length];
GenericsType[] receiverParameterizedTypes = actualReceiver.getGenericsTypes();
if (receiverParameterizedTypes==null) {
receiverParameterizedTypes = redirectReceiverTypes;
}
for (int i = 0; i < methodParameters.length; i++) {
Parameter methodParameter = methodParameters[i];
ClassNode paramType = methodParameter.getType();
if (paramType.isUsingGenerics()) {
GenericsType[] alignmentTypes = paramType.getGenericsTypes();
GenericsType[] genericsTypes = GenericsUtils.alignGenericTypes(redirectReceiverTypes, receiverParameterizedTypes, alignmentTypes);
if (genericsTypes.length==1) {
ClassNode parameterizedCN;
if (paramType.equals(OBJECT_TYPE)) {
parameterizedCN = genericsTypes[0].getType();
} else {
parameterizedCN= paramType.getPlainNodeReference();
parameterizedCN.setGenericsTypes(genericsTypes);
}
params[i] = new Parameter(
parameterizedCN,
methodParameter.getName()
);
} else {
params[i] = methodParameter;
}
} else {
params[i] = methodParameter;
}
}
/*if (m instanceof ExtensionMethodNode) {
// the parameter array we're using is the one from the extension
// but we want to return an array for the regular method
Parameter[] result = new Parameter[params.length-1];
// 0 is the receiver
// 1..n are what we want to return
System.arraycopy(params, 1, result, 0, result.length);
return result;
}*/
return params;
}
static boolean isUsingGenericsOrIsArrayUsingGenerics(ClassNode cn) {
return cn.isUsingGenerics() || cn.isArray() && cn.getComponentType().isUsingGenerics();
}
/**
* A DGM-like method which adds support for method calls which are handled
* specifically by the Groovy compiler.
*/
@SuppressWarnings("unused")
private static class ObjectArrayStaticTypesHelper {
public static <T> T getAt(T[] arr, int index) { return null;}
public static <T,U extends T> void putAt(T[] arr, int index, U object) { }
}
/**
* This class is used to make extension methods lookup faster. Basically, it will only
* collect the list of extension methods (see {@link ExtensionModule} if the list of
* extension modules has changed. It avoids recomputing the whole list each time we perform
* a method lookup.
*/
private static class ExtensionMethodCache {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private List<ExtensionModule> modules = Collections.emptyList();
private Map<String, List<MethodNode>> cachedMethods = null;
public Map<String, List<MethodNode>> getExtensionMethods() {
lock.readLock().lock();
MetaClassRegistry registry = GroovySystem.getMetaClassRegistry();
if (registry instanceof MetaClassRegistryImpl) {
MetaClassRegistryImpl impl = (MetaClassRegistryImpl) registry;
ExtensionModuleRegistry moduleRegistry = impl.getModuleRegistry();
if (!modules.equals(moduleRegistry.getModules())) {
lock.readLock().unlock();
lock.writeLock().lock();
try {
if (!modules.equals(moduleRegistry.getModules())) {
modules = moduleRegistry.getModules();
cachedMethods = getDGMMethods(registry);
}
} finally {
lock.writeLock().unlock();
lock.readLock().lock();
}
} else if (cachedMethods==null) {
lock.readLock().unlock();
lock.writeLock().lock();
try {
cachedMethods = getDGMMethods(registry);
} finally {
lock.writeLock().unlock();
lock.readLock().lock();
}
}
}
try {
return Collections.unmodifiableMap(cachedMethods);
} finally {
lock.readLock().unlock();
}
}
/**
* Returns a map which contains, as the key, the name of a class. The value
* consists of a list of MethodNode, one for each default groovy method found
* which is applicable for this class.
* @return
* @param registry
*/
private static Map<String, List<MethodNode>> getDGMMethods(final MetaClassRegistry registry) {
Set<Class> instanceExtClasses = new LinkedHashSet<Class>();
Set<Class> staticExtClasses = new LinkedHashSet<Class>();
if (registry instanceof MetaClassRegistryImpl) {
MetaClassRegistryImpl impl = (MetaClassRegistryImpl) registry;
List<ExtensionModule> modules = impl.getModuleRegistry().getModules();
for (ExtensionModule module : modules) {
if (module instanceof MetaInfExtensionModule) {
MetaInfExtensionModule extensionModule = (MetaInfExtensionModule) module;
instanceExtClasses.addAll(extensionModule.getInstanceMethodsExtensionClasses());
staticExtClasses.addAll(extensionModule.getStaticMethodsExtensionClasses());
}
}
}
Map<String, List<MethodNode>> methods = new HashMap<String, List<MethodNode>>();
Collections.addAll(instanceExtClasses, DefaultGroovyMethods.DGM_LIKE_CLASSES);
Collections.addAll(instanceExtClasses, DefaultGroovyMethods.additionals);
staticExtClasses.add(DefaultGroovyStaticMethods.class);
instanceExtClasses.add(ObjectArrayStaticTypesHelper.class);
List<Class> allClasses = new ArrayList<Class>(instanceExtClasses.size()+staticExtClasses.size());
allClasses.addAll(instanceExtClasses);
allClasses.addAll(staticExtClasses);
for (Class dgmLikeClass : allClasses) {
ClassNode cn = ClassHelper.makeWithoutCaching(dgmLikeClass, true);
for (MethodNode metaMethod : cn.getMethods()) {
Parameter[] types = metaMethod.getParameters();
if (metaMethod.isStatic() && metaMethod.isPublic() && types.length > 0
&& metaMethod.getAnnotations(Deprecated_TYPE).isEmpty()) {
Parameter[] parameters = new Parameter[types.length - 1];
System.arraycopy(types, 1, parameters, 0, parameters.length);
ExtensionMethodNode node = new ExtensionMethodNode(
metaMethod,
metaMethod.getName(),
metaMethod.getModifiers(),
metaMethod.getReturnType(),
parameters,
ClassNode.EMPTY_ARRAY, null,
staticExtClasses.contains(dgmLikeClass));
node.setGenericsTypes(metaMethod.getGenericsTypes());
ClassNode declaringClass = types[0].getType();
String declaringClassName = declaringClass.getName();
node.setDeclaringClass(declaringClass);
List<MethodNode> nodes = methods.get(declaringClassName);
if (nodes == null) {
nodes = new LinkedList<MethodNode>();
methods.put(declaringClassName, nodes);
}
nodes.add(node);
}
}
}
return methods;
}
}
/**
* @return true if the class node is either a GString or the LUB of String and GString.
*/
public static boolean isGStringOrGStringStringLUB(ClassNode node) {
return ClassHelper.GSTRING_TYPE.equals(node)
|| GSTRING_STRING_CLASSNODE.equals(node);
}
/**
* @param node the node to be tested
* @return true if the node is using generics types and one of those types is a gstring or string/gstring lub
*/
public static boolean isParameterizedWithGStringOrGStringString(ClassNode node) {
if (node.isArray()) return isParameterizedWithGStringOrGStringString(node.getComponentType());
if (node.isUsingGenerics()) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes!=null) {
for (GenericsType genericsType : genericsTypes) {
if (isGStringOrGStringStringLUB(genericsType.getType())) return true;
}
}
}
return node.getSuperClass() != null && isParameterizedWithGStringOrGStringString(node.getUnresolvedSuperClass());
}
/**
* @param node the node to be tested
* @return true if the node is using generics types and one of those types is a string
*/
public static boolean isParameterizedWithString(ClassNode node) {
if (node.isArray()) return isParameterizedWithString(node.getComponentType());
if (node.isUsingGenerics()) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes!=null) {
for (GenericsType genericsType : genericsTypes) {
if (STRING_TYPE.equals(genericsType.getType())) return true;
}
}
}
return node.getSuperClass() != null && isParameterizedWithString(node.getUnresolvedSuperClass());
}
public static boolean missesGenericsTypes(ClassNode cn) {
if (cn.isArray()) return missesGenericsTypes(cn.getComponentType());
if (cn.redirect().isUsingGenerics() && !cn.isUsingGenerics()) return true;
if (cn.isUsingGenerics()) {
if (cn.getGenericsTypes()==null) return true;
for (GenericsType genericsType : cn.getGenericsTypes()) {
if (genericsType.isPlaceholder()) return true;
}
}
return false;
}
}