groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cchamp...@apache.org
Subject [02/62] [abbrv] [partial] groovy git commit: Move Java source set into `src/main/java`
Date Sun, 17 Dec 2017 15:04:24 GMT
http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java b/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java
new file mode 100644
index 0000000..7839a59
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java
@@ -0,0 +1,1034 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.runtime;
+
+import groovy.lang.Closure;
+import groovy.lang.GString;
+import groovy.lang.GroovyObject;
+import groovy.lang.GroovyRuntimeException;
+import groovy.lang.MetaClass;
+import groovy.lang.MetaMethod;
+import org.codehaus.groovy.reflection.CachedClass;
+import org.codehaus.groovy.reflection.ParameterTypes;
+import org.codehaus.groovy.reflection.ReflectionCache;
+import org.codehaus.groovy.runtime.wrappers.Wrapper;
+import org.codehaus.groovy.util.FastArray;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * @author John Wilson
+ * @author Jochen Theodorou
+ */
+public class MetaClassHelper {
+
+    public static final Object[] EMPTY_ARRAY = {};
+    public static final Class[] EMPTY_TYPE_ARRAY = {};
+    public static final Object[] ARRAY_WITH_NULL = {null};
+    protected static final Logger LOG = Logger.getLogger(MetaClassHelper.class.getName());
+    private static final int MAX_ARG_LEN = 12;
+    private static final int
+            OBJECT_SHIFT = 23, INTERFACE_SHIFT = 0,
+            PRIMITIVE_SHIFT = 21, VARGS_SHIFT = 44;
+    /* dist binary layout:
+    * 0-20: interface
+    * 21-22: primitive dist
+    * 23-43: object dist
+    * 44-48: vargs penalty
+    */
+
+    public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+    public static boolean accessibleToConstructor(final Class at, final Constructor constructor) {
+        boolean accessible = false;
+        final int modifiers = constructor.getModifiers();
+        if (Modifier.isPublic(modifiers)) {
+            accessible = true;
+        } else if (Modifier.isPrivate(modifiers)) {
+            accessible = at.getName().equals(constructor.getName());
+        } else if (Modifier.isProtected(modifiers)) {
+            Boolean isAccessible = checkCompatiblePackages(at, constructor);
+            if (isAccessible != null) {
+                accessible = isAccessible;
+            } else {
+                boolean flag = false;
+                Class clazz = at;
+                while (!flag && clazz != null) {
+                    if (clazz.equals(constructor.getDeclaringClass())) {
+                        flag = true;
+                        break;
+                    }
+                    if (clazz.equals(Object.class)) {
+                        break;
+                    }
+                    clazz = clazz.getSuperclass();
+                }
+                accessible = flag;
+            }
+        } else {
+            Boolean isAccessible = checkCompatiblePackages(at, constructor);
+            if (isAccessible != null) {
+                accessible = isAccessible;
+            }
+        }
+        return accessible;
+    }
+
+    private static Boolean checkCompatiblePackages(Class at, Constructor constructor) {
+        if (at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null) {
+            return Boolean.TRUE;
+        }
+        if (at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null) {
+            return Boolean.FALSE;
+        }
+        if (at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null) {
+            return Boolean.FALSE;
+        }
+        if (at.getPackage().equals(constructor.getDeclaringClass().getPackage())) {
+            return Boolean.TRUE;
+        }
+        return null;
+    }
+
+    public static Object[] asWrapperArray(Object parameters, Class componentType) {
+        Object[] ret = null;
+        if (componentType == boolean.class) {
+            boolean[] array = (boolean[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == char.class) {
+            char[] array = (char[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == byte.class) {
+            byte[] array = (byte[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == int.class) {
+            int[] array = (int[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == short.class) {
+            short[] array = (short[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == long.class) {
+            long[] array = (long[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == double.class) {
+            double[] array = (double[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        } else if (componentType == float.class) {
+            float[] array = (float[]) parameters;
+            ret = new Object[array.length];
+            for (int i = 0; i < array.length; i++) {
+                ret[i] = array[i];
+            }
+        }
+
+        return ret;
+    }
+
+
+    /**
+     * @param list          the original list
+     * @param parameterType the resulting array type
+     * @return the constructed array
+     */
+    public static Object asPrimitiveArray(List list, Class parameterType) {
+        Class arrayType = parameterType.getComponentType();
+        Object objArray = Array.newInstance(arrayType, list.size());
+        for (int i = 0; i < list.size(); i++) {
+            Object obj = list.get(i);
+            if (arrayType.isPrimitive()) {
+                if (obj instanceof Integer) {
+                    Array.setInt(objArray, i, (Integer) obj);
+                } else if (obj instanceof Double) {
+                    Array.setDouble(objArray, i, (Double) obj);
+                } else if (obj instanceof Boolean) {
+                    Array.setBoolean(objArray, i, (Boolean) obj);
+                } else if (obj instanceof Long) {
+                    Array.setLong(objArray, i, (Long) obj);
+                } else if (obj instanceof Float) {
+                    Array.setFloat(objArray, i, (Float) obj);
+                } else if (obj instanceof Character) {
+                    Array.setChar(objArray, i, (Character) obj);
+                } else if (obj instanceof Byte) {
+                    Array.setByte(objArray, i, (Byte) obj);
+                } else if (obj instanceof Short) {
+                    Array.setShort(objArray, i, (Short) obj);
+                }
+            } else {
+                Array.set(objArray, i, obj);
+            }
+        }
+        return objArray;
+    }
+
+    private static final Class[] PRIMITIVES = {
+            boolean.class,
+            Boolean.class,
+            byte.class,
+            Byte.class,
+            short.class,
+            Short.class,
+            char.class,
+            Character.class,
+            int.class,
+            Integer.class,
+            long.class,
+            Long.class,
+            BigInteger.class,
+            float.class,
+            Float.class,
+            double.class,
+            Double.class,
+            BigDecimal.class,
+            Number.class,
+            Object.class
+    };
+
+    private static final int[][] PRIMITIVE_DISTANCE_TABLE = {
+            //                    0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19
+            /*boolean[0]*/      { 0,  1,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  2,},
+            /*Boolean[1]*/      { 1,  0,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,  2,},
+            /*byte[2]*/         {18, 19,  0,  1,  2,  3, 16, 17,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,},
+            /*Byte[3]*/         {18, 19,  1,  0,  2,  3, 16, 17,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,},
+            /*short[4]*/        {18, 19, 14, 15,  0,  1, 16, 17,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,},
+            /*Short[5]*/        {18, 19, 14, 15,  1,  0, 16, 17,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,},
+            /*char[6]*/         {18, 19, 16, 17, 14, 15,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,},
+            /*Character[7]*/    {18, 19, 16, 17, 14, 15,  1,  0,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,},
+            /*int[8]*/          {18, 19, 14, 15, 12, 13, 16, 17,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,},
+            /*Integer[9]*/      {18, 19, 14, 15, 12, 13, 16, 17,  1,  0,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11,},
+            /*long[10]*/        {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,},
+            /*Long[11]*/        {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  1,  0,  2,  3,  4,  5,  6,  7,  8,  9,},
+            /*BigInteger[12]*/  {18, 19,  9, 10,  7,  8, 16, 17,  5,  6,  3,  4,  0, 14, 15, 12, 13, 11,  1,  2,},
+            /*float[13]*/       {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  0,  1,  2,  3,  4,  5,  6,},
+            /*Float[14]*/       {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  1,  0,  2,  3,  4,  5,  6,},
+            /*double[15]*/      {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  5,  6,  0,  1,  2,  3,  4,},
+            /*Double[16]*/      {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  5,  6,  1,  0,  2,  3,  4,},
+            /*BigDecimal[17]*/  {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  5,  6,  3,  4,  0,  1,  2,},
+            /*Number[18]*/      {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  5,  6,  3,  4,  2,  0,  1,},
+            /*Object[19]*/      {18, 19, 14, 15, 12, 13, 16, 17, 10, 11,  8,  9,  7,  5,  6,  3,  4,  2,  1,  0,},
+    };
+
+    private static int getPrimitiveIndex(Class c) {
+        for (byte i = 0; i < PRIMITIVES.length; i++) {
+            if (PRIMITIVES[i] == c) return i;
+        }
+        return -1;
+    }
+
+    private static int getPrimitiveDistance(Class from, Class to) {
+        // we know here that from!=to, so a distance of 0 is never valid
+        // get primitive type indexes
+        int fromIndex = getPrimitiveIndex(from);
+        int toIndex = getPrimitiveIndex(to);
+        if (fromIndex == -1 || toIndex == -1) return -1;
+        return PRIMITIVE_DISTANCE_TABLE[toIndex][fromIndex];
+    }
+
+    private static int getMaximumInterfaceDistance(Class c, Class interfaceClass) {
+        // -1 means a mismatch
+        if (c == null) return -1;
+        // 0 means a direct match
+        if (c == interfaceClass) return 0;
+        Class[] interfaces = c.getInterfaces();
+        int max = -1;
+        for (Class 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);
+        if (superClassMax != -1) superClassMax++;
+        return Math.max(max, superClassMax);
+    }
+
+    private static long calculateParameterDistance(Class argument, CachedClass parameter) {
+        /**
+         * note: when shifting with 32 bit, you should only shift on a long. If you do
+         *       that with an int, then i==(i<<32), which means you loose the shift
+         *       information
+         */
+
+        if (parameter.getTheClass() == argument) return 0;
+
+        if (parameter.isInterface()) {
+            int dist = getMaximumInterfaceDistance(argument, parameter.getTheClass()) << INTERFACE_SHIFT;
+            if (dist>-1 || !(argument!=null && Closure.class.isAssignableFrom(argument))) {
+                return dist;
+            } // else go to object case
+        }
+
+        long objectDistance = 0;
+        if (argument != null) {
+            long pd = getPrimitiveDistance(parameter.getTheClass(), argument);
+            if (pd != -1) return pd << PRIMITIVE_SHIFT;
+
+            // add one to dist to be sure interfaces are preferred
+            objectDistance += PRIMITIVES.length + 1;
+
+            // GROOVY-5114 : if we have to choose between two methods
+            // foo(Object[]) and foo(Object) and that the argument is an array type
+            // then the array version should be preferred
+            if (argument.isArray() && !parameter.isArray) {
+                objectDistance+=4;
+            }
+            Class clazz = ReflectionCache.autoboxType(argument);
+            while (clazz != null) {
+                if (clazz == parameter.getTheClass()) break;
+                if (clazz == GString.class && parameter.getTheClass() == String.class) {
+                    objectDistance += 2;
+                    break;
+                }
+                clazz = clazz.getSuperclass();
+                objectDistance += 3;
+            }
+        } else {
+            // choose the distance to Object if a parameter is null
+            // this will mean that Object is preferred over a more
+            // specific type
+            Class clazz = parameter.getTheClass();
+            if (clazz.isPrimitive()) {
+                objectDistance += 2;
+            } else {
+                while (clazz != Object.class && clazz != null) {
+                    clazz = clazz.getSuperclass();
+                    objectDistance += 2;
+                }
+            }
+        }
+        return objectDistance << OBJECT_SHIFT;
+    }
+
+    public static long calculateParameterDistance(Class[] arguments, ParameterTypes pt) {
+        CachedClass[] parameters = pt.getParameterTypes();
+        if (parameters.length == 0) return 0;
+
+        long ret = 0;
+        int noVargsLength = parameters.length - 1;
+
+        // if the number of parameters does not match we have 
+        // a vargs usage
+        //
+        // case A: arguments.length<parameters.length
+        //
+        //         In this case arguments.length is always equal to
+        //         noVargsLength because only the last parameter
+        //         might be a optional vargs parameter
+        //
+        //         VArgs penalty: 1l
+        //
+        // case B: arguments.length>parameters.length
+        //
+        //         In this case all arguments with a index bigger than
+        //         paramMinus1 are part of the vargs, so a 
+        //         distance calculation needs to be done against 
+        //         parameters[noVargsLength].getComponentType()
+        //
+        //         VArgs penalty: 2l+arguments.length-parameters.length
+        //
+        // case C: arguments.length==parameters.length && 
+        //         isAssignableFrom( parameters[noVargsLength],
+        //                           arguments[noVargsLength] )
+        //
+        //         In this case we have no vargs, so calculate directly
+        //
+        //         VArgs penalty: 0l
+        //
+        // case D: arguments.length==parameters.length && 
+        //         !isAssignableFrom( parameters[noVargsLength],
+        //                            arguments[noVargsLength] )
+        //
+        //         In this case we have a vargs case again, we need 
+        //         to calculate arguments[noVargsLength] against
+        //         parameters[noVargsLength].getComponentType
+        //
+        //         VArgs penalty: 2l
+        //
+        //         This gives: VArgs_penalty(C)<VArgs_penalty(A)
+        //                     VArgs_penalty(A)<VArgs_penalty(D)
+        //                     VArgs_penalty(D)<VArgs_penalty(B)
+
+        /**
+         * In general we want to match the signature that allows us to use
+         * as less arguments for the vargs part as possible. That means the
+         * longer signature usually wins if both signatures are vargs, while
+         * vargs looses always against a signature without vargs.
+         *
+         *  A vs B :
+         *      def foo(Object[] a) {1}     -> case B
+         *      def foo(a,b,Object[] c) {2} -> case A
+         *      assert foo(new Object(),new Object()) == 2
+         *  --> A preferred over B
+         *
+         *  A vs C :
+         *      def foo(Object[] a) {1}     -> case B
+         *      def foo(a,b)        {2}     -> case C
+         *      assert foo(new Object(),new Object()) == 2
+         *  --> C preferred over A
+         *
+         *  A vs D :
+         *      def foo(Object[] a) {1}     -> case D
+         *      def foo(a,Object[] b) {2}   -> case A
+         *      assert foo(new Object()) == 2
+         *  --> A preferred over D
+         *
+         *  This gives C<A<B,D
+         *
+         *  B vs C :
+         *      def foo(Object[] a) {1}     -> case B
+         *      def foo(a,b) {2}            -> case C
+         *      assert foo(new Object(),new Object()) == 2
+         *  --> C preferred over B, matches C<A<B,D
+         *
+         *  B vs D :
+         *      def foo(Object[] a)   {1}   -> case B
+         *      def foo(a,Object[] b) {2}   -> case D
+         *      assert foo(new Object(),new Object()) == 2
+         *  --> D preferred over B
+         *
+         *  This gives C<A<D<B 
+         */
+
+        // first we calculate all arguments, that are for sure not part
+        // of vargs.  Since the minimum for arguments is noVargsLength
+        // we can safely iterate to this point
+        for (int i = 0; i < noVargsLength; i++) {
+            ret += calculateParameterDistance(arguments[i], parameters[i]);
+        }
+
+        if (arguments.length == parameters.length) {
+            // case C&D, we use baseType to calculate and set it
+            // to the value we need according to case C and D
+            CachedClass baseType = parameters[noVargsLength]; // case C
+            if (!parameters[noVargsLength].isAssignableFrom(arguments[noVargsLength])) {
+                baseType = ReflectionCache.getCachedClass(baseType.getTheClass().getComponentType()); // case D
+                ret += 2L << VARGS_SHIFT; // penalty for vargs
+            }
+            ret += calculateParameterDistance(arguments[noVargsLength], baseType);
+        } else if (arguments.length > parameters.length) {
+            // case B
+            // we give our a vargs penalty for each exceeding argument and iterate
+            // by using parameters[noVargsLength].getComponentType()
+            ret += (2L + arguments.length - parameters.length) << VARGS_SHIFT; // penalty for vargs
+            CachedClass vargsType = ReflectionCache.getCachedClass(parameters[noVargsLength].getTheClass().getComponentType());
+            for (int i = noVargsLength; i < arguments.length; i++) {
+                ret += calculateParameterDistance(arguments[i], vargsType);
+            }
+        } else {
+            // case A
+            // we give a penalty for vargs, since we have no direct
+            // match for the last argument
+            ret += 1L << VARGS_SHIFT;
+        }
+
+        return ret;
+    }
+
+    /**
+     * This is the complement to the java.beans.Introspector.decapitalize(String) method.
+     * We handle names that begin with an initial lowerCase followed by upperCase specially
+     * (which is to make no change).
+     * See GROOVY-3211.
+     *
+     * @param property the property name to capitalize
+     * @return the name capitalized, except when we don't
+     */
+    public static String capitalize(final String property) {
+        final String rest = property.substring(1);
+
+        // Funky rule so that names like 'pNAME' will still work.
+        if (Character.isLowerCase(property.charAt(0)) && (rest.length() > 0) && Character.isUpperCase(rest.charAt(0))) {
+            return property;
+        }
+
+        return property.substring(0, 1).toUpperCase() + rest;
+    }
+
+    /**
+     * @param methods the methods to choose from
+     * @return the method with 1 parameter which takes the most general type of
+     *         object (e.g. Object)
+     */
+    public static Object chooseEmptyMethodParams(FastArray methods) {
+        Object vargsMethod = null;
+        final int len = methods.size();
+        final Object[] data = methods.getArray();
+        for (int i = 0; i != len; ++i) {
+            Object method = data[i];
+            final ParameterTypes pt = (ParameterTypes) method;
+            CachedClass[] paramTypes = pt.getParameterTypes();
+            int paramLength = paramTypes.length;
+            if (paramLength == 0) {
+                return method;
+            } else if (paramLength == 1 && pt.isVargsMethod(EMPTY_ARRAY)) {
+                vargsMethod = method;
+            }
+        }
+        return vargsMethod;
+    }
+
+    /**
+     * Warning: this method does not choose properly if multiple methods with
+     * the same distance are encountered
+     * @param methods the methods to choose from
+     * @return the method with 1 parameter which takes the most general type of
+     *         object (e.g. Object) ignoring primitive types
+     * @deprecated
+     */
+    @Deprecated
+    public static Object chooseMostGeneralMethodWith1NullParam(FastArray methods) {
+        // let's look for methods with 1 argument which matches the type of the
+        // arguments
+        CachedClass closestClass = null;
+        CachedClass closestVargsClass = null;
+        Object answer = null;
+        int closestDist = -1;
+        final int len = methods.size();
+        for (int i = 0; i != len; ++i) {
+            final Object[] data = methods.getArray();
+            Object method = data[i];
+            final ParameterTypes pt = (ParameterTypes) method;
+            CachedClass[] paramTypes = pt.getParameterTypes();
+            int paramLength = paramTypes.length;
+            if (paramLength == 0 || paramLength > 2) continue;
+
+            CachedClass theType = paramTypes[0];
+            if (theType.isPrimitive) continue;
+
+            if (paramLength == 2) {
+                if (!pt.isVargsMethod(ARRAY_WITH_NULL)) continue;
+                if (closestClass == null) {
+                    closestVargsClass = paramTypes[1];
+                    closestClass = theType;
+                    answer = method;
+                } else if (closestClass.getTheClass() == theType.getTheClass()) {
+                    if (closestVargsClass == null) continue;
+                    CachedClass newVargsClass = paramTypes[1];
+                    if (isAssignableFrom(newVargsClass.getTheClass(), closestVargsClass.getTheClass())) {
+                        closestVargsClass = newVargsClass;
+                        answer = method;
+                    }
+                } else if (isAssignableFrom(theType.getTheClass(), closestClass.getTheClass())) {
+                    closestVargsClass = paramTypes[1];
+                    closestClass = theType;
+                    answer = method;
+                }
+            } else {
+                if (closestClass == null || isAssignableFrom(theType.getTheClass(), closestClass.getTheClass())) {
+                    closestVargsClass = null;
+                    closestClass = theType;
+                    answer = method;
+                    closestDist = -1;
+                } else {
+                    // closestClass and theType are not in a subtype relation, we need
+                    // to check the distance to Object
+                    if (closestDist == -1) closestDist = closestClass.getSuperClassDistance();
+                    int newDist = theType.getSuperClassDistance();
+                    if (newDist < closestDist) {
+                        closestDist = newDist;
+                        closestVargsClass = null;
+                        closestClass = theType;
+                        answer = method;
+                    }
+                }
+            }
+        }
+        return answer;
+    }
+
+    // 
+
+    /**
+     * @param list   a list of MetaMethods
+     * @param method the MetaMethod of interest
+     * @return true if a method of the same matching prototype was found in the
+     *         list
+     */
+    public static boolean containsMatchingMethod(List list, MetaMethod method) {
+        for (Object aList : list) {
+            MetaMethod aMethod = (MetaMethod) aList;
+            CachedClass[] params1 = aMethod.getParameterTypes();
+            CachedClass[] params2 = method.getParameterTypes();
+            if (params1.length == params2.length) {
+                boolean matches = true;
+                for (int i = 0; i < params1.length; i++) {
+                    if (params1[i] != params2[i]) {
+                        matches = false;
+                        break;
+                    }
+                }
+                if (matches) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * param instance array to the type array
+     *
+     * @param args the arguments
+     * @return the types of the arguments
+     */
+    public static Class[] convertToTypeArray(Object[] args) {
+        if (args == null)
+            return null;
+        int s = args.length;
+        Class[] ans = new Class[s];
+        for (int i = 0; i < s; i++) {
+            Object o = args[i];
+            ans[i] = getClassWithNullAndWrapper(o);
+        }
+        return ans;
+    }
+
+    public static Object makeCommonArray(Object[] arguments, int offset, Class fallback) {
+        // arguments.length>0 && !=null
+        Class baseClass = null;
+        for (int i = offset; i < arguments.length; i++) {
+            if (arguments[i] == null) continue;
+            Class argClass = arguments[i].getClass();
+            if (baseClass == null) {
+                baseClass = argClass;
+            } else {
+                for (; baseClass != Object.class; baseClass = baseClass.getSuperclass()) {
+                    if (baseClass.isAssignableFrom(argClass)) break;
+                }
+            }
+        }
+        if (baseClass == null) {
+            // all arguments were null
+            baseClass = fallback;
+        }
+        /*
+         * If no specific super class has been found and type fallback is an interface, check if all arg classes 
+         * implement it. If yes, then that interface is the common type across arguments.
+         */
+        if (baseClass == Object.class && fallback.isInterface()) {
+            int tmpCount = 0;
+            for (int i = offset; i < arguments.length; i++) {
+                if (arguments[i] != null) {
+                    Class tmpClass;
+                    Set<Class> intfs = new HashSet<Class>();
+                    tmpClass = arguments[i].getClass();
+                    for (; tmpClass != Object.class; tmpClass = tmpClass.getSuperclass()) {
+                        intfs.addAll(Arrays.asList(tmpClass.getInterfaces()));
+                    }
+                    if (intfs.contains(fallback)) {
+                        tmpCount++;
+                    }
+                }
+            }
+            // all arg classes implement interface fallback, so use that as the array component type
+            if (tmpCount == arguments.length - offset) {
+                baseClass = fallback;
+            }
+        }
+        Object result = makeArray(null, baseClass, arguments.length - offset);
+        System.arraycopy(arguments, offset, result, 0, arguments.length - offset);
+        return result;
+    }
+
+    public static Object makeArray(Object obj, Class secondary, int length) {
+        Class baseClass = secondary;
+        if (obj != null) {
+            baseClass = obj.getClass();
+        }
+        /*if (GString.class.isAssignableFrom(baseClass)) {
+              baseClass = GString.class;
+          }*/
+        return Array.newInstance(baseClass, length);
+    }
+
+    public static GroovyRuntimeException createExceptionText(String init, MetaMethod method, Object object, Object[] args, Throwable reason, boolean setReason) {
+        return new GroovyRuntimeException(
+                init
+                        + method
+                        + " on: "
+                        + object
+                        + " with arguments: "
+                        + InvokerHelper.toString(args)
+                        + " reason: "
+                        + reason,
+                setReason ? reason : null);
+    }
+
+    protected static String getClassName(Object object) {
+        if (object == null) return null;
+        return (object instanceof Class) ? ((Class) object).getName() : object.getClass().getName();
+    }
+
+    /**
+     * Returns a callable object for the given method name on the object.
+     * The object acts like a Closure in that it can be called, like a closure
+     * and passed around - though really its a method pointer, not a closure per se.
+     *
+     * @param object     the object containing the method
+     * @param methodName the method of interest
+     * @return the resulting closure-like method pointer
+     */
+    public static Closure getMethodPointer(Object object, String methodName) {
+        return new MethodClosure(object, methodName);
+    }
+
+    public static boolean isAssignableFrom(Class classToTransformTo, Class classToTransformFrom) {
+        if (classToTransformTo == classToTransformFrom
+                || classToTransformFrom == null
+                || classToTransformTo == Object.class) {
+            return true;
+        }
+
+        classToTransformTo = ReflectionCache.autoboxType(classToTransformTo);
+        classToTransformFrom = ReflectionCache.autoboxType(classToTransformFrom);
+        if (classToTransformTo == classToTransformFrom) return true;
+
+        // note: there is no coercion for boolean and char. Range matters, precision doesn't
+        if (classToTransformTo == Integer.class) {
+            if (classToTransformFrom == Short.class
+                    || classToTransformFrom == Byte.class
+                    || classToTransformFrom == BigInteger.class)
+                return true;
+        } else if (classToTransformTo == Double.class) {
+            if (classToTransformFrom == Integer.class
+                    || classToTransformFrom == Long.class
+                    || classToTransformFrom == Short.class
+                    || classToTransformFrom == Byte.class
+                    || classToTransformFrom == Float.class
+                    || classToTransformFrom == BigDecimal.class
+                    || classToTransformFrom == BigInteger.class)
+                return true;
+        } else if (classToTransformTo == BigDecimal.class) {
+            if (classToTransformFrom == Double.class
+                    || classToTransformFrom == Integer.class
+                    || classToTransformFrom == Long.class
+                    || classToTransformFrom == Short.class
+                    || classToTransformFrom == Byte.class
+                    || classToTransformFrom == Float.class
+                    || classToTransformFrom == BigInteger.class)
+                return true;
+        } else if (classToTransformTo == BigInteger.class) {
+            if (classToTransformFrom == Integer.class
+                    || classToTransformFrom == Long.class
+                    || classToTransformFrom == Short.class
+                    || classToTransformFrom == Byte.class)
+                return true;
+        } else if (classToTransformTo == Long.class) {
+            if (classToTransformFrom == Integer.class
+                    || classToTransformFrom == Short.class
+                    || classToTransformFrom == Byte.class)
+                return true;
+        } else if (classToTransformTo == Float.class) {
+            if (classToTransformFrom == Integer.class
+                    || classToTransformFrom == Long.class
+                    || classToTransformFrom == Short.class
+                    || classToTransformFrom == Byte.class)
+                return true;
+        } else if (classToTransformTo == Short.class) {
+            if (classToTransformFrom == Byte.class)
+                return true;
+        } else if (classToTransformTo == String.class) {
+            if (GString.class.isAssignableFrom(classToTransformFrom)) {
+                return true;
+            }
+        }
+
+        return ReflectionCache.isAssignableFrom(classToTransformTo, classToTransformFrom);
+    }
+
+    public static boolean isGenericSetMethod(MetaMethod method) {
+        return (method.getName().equals("set"))
+                && method.getParameterTypes().length == 2;
+    }
+
+    protected static boolean isSuperclass(Class clazz, Class superclass) {
+        while (clazz != null) {
+            if (clazz == superclass) return true;
+            clazz = clazz.getSuperclass();
+        }
+        return false;
+    }
+
+    public static boolean parametersAreCompatible(Class[] arguments, Class[] parameters) {
+        if (arguments.length != parameters.length) return false;
+        for (int i = 0; i < arguments.length; i++) {
+            if (!isAssignableFrom(parameters[i], arguments[i])) return false;
+        }
+        return true;
+    }
+
+    public static void logMethodCall(Object object, String methodName, Object[] arguments) {
+        String className = getClassName(object);
+        String logname = "methodCalls." + className + "." + methodName;
+        Logger objLog = Logger.getLogger(logname);
+        if (!objLog.isLoggable(Level.FINER)) return;
+        StringBuilder msg = new StringBuilder(methodName);
+        msg.append("(");
+        if (arguments != null) {
+            for (int i = 0; i < arguments.length;) {
+                msg.append(normalizedValue(arguments[i]));
+                if (++i < arguments.length) {
+                    msg.append(",");
+                }
+            }
+        }
+        msg.append(")");
+        objLog.logp(Level.FINER, className, msg.toString(), "called from MetaClass.invokeMethod");
+    }
+
+    protected static String normalizedValue(Object argument) {
+        String value;
+        try {
+            value = argument.toString();
+            if (value.length() > MAX_ARG_LEN) {
+                value = value.substring(0, MAX_ARG_LEN - 2) + "..";
+            }
+            if (argument instanceof String) {
+                value = "\'" + value + "\'";
+            }
+        } catch (Exception e) {
+            value = shortName(argument);
+        }
+        return value;
+    }
+
+    protected static String shortName(Object object) {
+        if (object == null || object.getClass() == null) return "unknownClass";
+        String name = getClassName(object);
+        if (name == null) return "unknownClassName"; // *very* defensive...
+        int lastDotPos = name.lastIndexOf('.');
+        if (lastDotPos < 0 || lastDotPos >= name.length() - 1) return name;
+        return name.substring(lastDotPos + 1);
+    }
+
+    public static Class[] wrap(Class[] classes) {
+        Class[] wrappedArguments = new Class[classes.length];
+        for (int i = 0; i < wrappedArguments.length; i++) {
+            Class c = classes[i];
+            if (c == null) continue;
+            if (c.isPrimitive()) {
+                if (c == Integer.TYPE) {
+                    c = Integer.class;
+                } else if (c == Byte.TYPE) {
+                    c = Byte.class;
+                } else if (c == Long.TYPE) {
+                    c = Long.class;
+                } else if (c == Double.TYPE) {
+                    c = Double.class;
+                } else if (c == Float.TYPE) {
+                    c = Float.class;
+                }
+            } else if (isSuperclass(c, GString.class)) {
+                c = String.class;
+            }
+            wrappedArguments[i] = c;
+        }
+        return wrappedArguments;
+    }
+
+    public static boolean sameClasses(Class[] params, Object[] arguments, boolean weakNullCheck) {
+        if (params.length != arguments.length)
+            return false;
+
+        for (int i = params.length - 1; i >= 0; i--) {
+            Object arg = arguments[i];
+            Class compareClass = getClassWithNullAndWrapper(arg);
+            if (params[i] != compareClass) return false;
+        }
+
+        return true;
+    }
+
+    private static Class getClassWithNullAndWrapper(Object arg) {
+        if (arg == null) return null;
+        if (arg instanceof Wrapper) {
+            Wrapper w = (Wrapper) arg;
+            return w.getType();
+        }
+        return arg.getClass();
+    }
+
+    public static boolean sameClasses(Class[] params, Object[] arguments) {
+        if (params.length != arguments.length)
+            return false;
+
+        for (int i = params.length - 1; i >= 0; i--) {
+            Object arg = arguments[i];
+            if (arg == null) {
+                if (params[i] != null)
+                    return false;
+            } else {
+                if (params[i] != getClassWithNullAndWrapper(arg))
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static boolean sameClasses(Class[] params) {
+        if (params.length != 0)
+            return false;
+
+        return true;
+    }
+
+    public static boolean sameClasses(Class[] params, Object arg1) {
+        if (params.length != 1)
+            return false;
+
+        if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
+
+        return true;
+    }
+
+    public static boolean sameClasses(Class[] params, Object arg1, Object arg2) {
+        if (params.length != 2)
+            return false;
+
+        if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
+        if (params[1] != getClassWithNullAndWrapper(arg2)) return false;
+
+        return true;
+    }
+
+    public static boolean sameClasses(Class[] params, Object arg1, Object arg2, Object arg3) {
+        if (params.length != 3)
+            return false;
+
+        if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
+        if (params[1] != getClassWithNullAndWrapper(arg2)) return false;
+        if (params[2] != getClassWithNullAndWrapper(arg3)) return false;
+
+        return true;
+    }
+
+    public static boolean sameClasses(Class[] params, Object arg1, Object arg2, Object arg3, Object arg4) {
+        if (params.length != 4)
+            return false;
+
+        if (params[0] != getClassWithNullAndWrapper(arg1)) return false;
+        if (params[1] != getClassWithNullAndWrapper(arg2)) return false;
+        if (params[2] != getClassWithNullAndWrapper(arg3)) return false;
+        if (params[3] != getClassWithNullAndWrapper(arg4)) return false;
+        
+        return true;
+    }
+
+    public static boolean sameClass(Class[] params, Object arg) {
+        return params[0] == getClassWithNullAndWrapper(arg);
+
+    }
+
+    public static Class[] castArgumentsToClassArray(Object[] argTypes) {
+        if (argTypes == null) return EMPTY_CLASS_ARRAY;
+        Class[] classes = new Class[argTypes.length];
+        for (int i = 0; i < argTypes.length; i++) {
+            Object argType = argTypes[i];
+            if (argType instanceof Class) {
+                classes[i] = (Class) argType;
+            } else if (argType == null) {
+                classes[i] = null;
+            } else {
+//                throw new IllegalArgumentException("Arguments to method [respondsTo] must be of type java.lang.Class!");
+                classes[i] = argType.getClass();
+            }
+        }
+        return classes;
+    }
+
+    public static void unwrap(Object[] arguments) {
+        //
+        // Temp code to ignore wrapped parameters
+        // The New MOP will deal with these properly
+        //
+        for (int i = 0; i != arguments.length; i++) {
+            if (arguments[i] instanceof Wrapper) {
+                arguments[i] = ((Wrapper) arguments[i]).unwrap();
+            }
+        }
+    }
+
+    /**
+     * Sets the meta class for an object, by delegating to the appropriate
+     * {@link DefaultGroovyMethods} helper method. This method was introduced as
+     * a breaking change in 2.0 to solve rare cases of stack overflow. See GROOVY-5285.
+     *
+     * The method is named doSetMetaClass in order to prevent misusages. Do not use
+     * this method directly unless you know what you do.
+     *
+     * @param self the object for which to set the meta class
+     * @param mc the metaclass
+     */
+    public static void doSetMetaClass(Object self, MetaClass mc) {
+        if (self instanceof GroovyObject) {
+            DefaultGroovyMethods.setMetaClass((GroovyObject)self, mc);
+        } else {
+            DefaultGroovyMethods.setMetaClass(self, mc);
+        }
+    }
+
+    /**
+     * Converts a String into a standard property name.
+     *
+     * @param prop the original name
+     * @return the converted name
+     */
+    public static String convertPropertyName(String prop) {
+        if (Character.isDigit(prop.charAt(0))) {
+            return prop;
+        }
+        return java.beans.Introspector.decapitalize(prop);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java b/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
new file mode 100644
index 0000000..7ead0c7
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
@@ -0,0 +1,88 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.runtime;
+
+import groovy.lang.Closure;
+import groovy.lang.MetaMethod;
+
+import java.io.IOException;
+import java.util.List;
+
+
+/**
+ * Represents a method on an object using a closure which can be invoked
+ * at any time
+ * 
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public class MethodClosure extends Closure {
+
+    public static boolean ALLOW_RESOLVE = false;
+
+    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+    private final String method;
+
+    public MethodClosure(Object owner, String method) {
+        super(owner);
+        this.method = method;
+
+        final Class clazz = owner.getClass()==Class.class?(Class) owner:owner.getClass();
+        
+        maximumNumberOfParameters = 0;
+        parameterTypes = EMPTY_CLASS_ARRAY;
+
+        List<MetaMethod> methods = InvokerHelper.getMetaClass(clazz).respondsTo(owner, method);
+        
+        for(MetaMethod m : methods) {
+            if (m.getParameterTypes().length > maximumNumberOfParameters) {
+                Class[] pt = m.getNativeParameterTypes();
+                maximumNumberOfParameters = pt.length;
+                parameterTypes = pt;
+            }
+        }
+    }
+    
+    public String getMethod() {
+        return method;
+    }
+
+    protected Object doCall(Object arguments) {
+        return InvokerHelper.invokeMethod(getOwner(), method, arguments);
+    }
+
+    private Object readResolve() {
+        if (ALLOW_RESOLVE) {
+            return this;
+        }
+        throw new UnsupportedOperationException();
+    }
+
+    private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        if (ALLOW_RESOLVE) {
+            stream.defaultReadObject();
+        }
+        throw new UnsupportedOperationException();
+    }
+    
+    public Object getProperty(String property) {
+        if ("method".equals(property)) {
+            return getMethod();
+        } else  return super.getProperty(property);        
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/MethodKey.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/MethodKey.java b/src/main/java/org/codehaus/groovy/runtime/MethodKey.java
new file mode 100644
index 0000000..d03b85a
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/MethodKey.java
@@ -0,0 +1,133 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.runtime;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * An abstract base class for a key used for comparators and Map keys to lookup a method by
+ * name and parameter types
+ * 
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public abstract class MethodKey {
+
+    private int hash;
+    private final String name;
+    private final Class sender;
+    private final boolean isCallToSuper;
+    
+    public MethodKey(Class sender, String name, boolean isCallToSuper) {
+        this.sender = sender;
+        this.name = name;
+        this.isCallToSuper = isCallToSuper;
+    }
+
+    /**
+     * Creates an immutable copy that we can cache. 
+     */
+    public MethodKey createCopy() {
+        int size = getParameterCount();
+        Class[] paramTypes = new Class[size];
+        for (int i = 0; i < size; i++) {
+            paramTypes[i] = getParameterType(i);
+        }
+        return new DefaultMethodKey(sender, name, paramTypes, isCallToSuper);
+    }
+
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        else if (that instanceof MethodKey) {
+            return equals((MethodKey) that);
+        }
+        return false;
+    }
+
+    public boolean equals(MethodKey that) {
+      int size;
+      if (sender!=that.sender) return false;
+      if (isCallToSuper!=that.isCallToSuper) return false;
+      if (!name.equals(that.name)) return false;
+      if ((size = getParameterCount()) != that.getParameterCount()) return false;
+      
+      for (int i = 0; i < size; i++) {
+          if (getParameterType(i) != that.getParameterType(i)) {
+              return false;
+          }
+      }
+      return true;
+    }
+
+    public int hashCode() {
+        if (hash == 0) {
+            hash = createHashCode();
+            if (hash == 0) {
+                hash = 0xcafebabe;
+            }
+        }
+        return hash;
+    }
+
+    public String toString() {
+        return super.toString() + "[name:" + name + "; params:" + getParamterTypes();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List getParamterTypes() {
+        int size = getParameterCount();
+        if (size <= 0) {
+            return Collections.EMPTY_LIST;
+        }
+        List params = new ArrayList(size);
+        for (int i = 0; i < size; i++) {
+            params.add(getParameterType(i));
+        }
+        return params;
+    }
+
+    public abstract int getParameterCount();
+    public abstract Class getParameterType(int index);
+
+    protected int createHashCode() {
+        int answer = name.hashCode();
+        int size = getParameterCount();
+
+        /** @todo we should use the real Josh Bloch algorithm here */
+
+        // can't remember the exact Josh Bloch algorithm and I've not got the book handy
+        // but its something like this IIRC
+        for (int i = 0; i < size; i++) {
+            answer *= 37;
+            answer += 1 + getParameterType(i).hashCode();
+        }
+        answer *= 37;
+        answer += isCallToSuper?1:0;
+        answer *= 37;
+        answer += 1 + sender.hashCode();
+        return answer;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/MethodRankHelper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/MethodRankHelper.java b/src/main/java/org/codehaus/groovy/runtime/MethodRankHelper.java
new file mode 100644
index 0000000..295cbe8
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/MethodRankHelper.java
@@ -0,0 +1,562 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.runtime;
+
+import groovy.lang.MetaMethod;
+import groovy.lang.MetaProperty;
+import org.codehaus.groovy.reflection.CachedClass;
+import org.codehaus.groovy.reflection.ClassInfo;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class for MissingMethodException, MissingPropertyException etc.
+ * This class contains methods assisting in ranking and listing probable intended
+ * methods/fields when a exception is thrown.
+ *
+ * @author Hjalmar Ekengren
+ */
+public class MethodRankHelper{
+    //These are the costs for the various edit operations
+    //they are used by the two DamerauLevenshtein implementations
+    public static final int DL_SUBSTITUTION = 10;
+    public static final int DL_DELETE = 10; //This is also the cost for a insert
+    public static final int DL_TRANSPOSITION = 5;
+    public static final int DL_CASE = 5;
+    
+    public static final int MAX_RECOMENDATIONS = 5;
+    public static final int MAX_METHOD_SCORE = 50;
+    public static final int MAX_CONSTRUCTOR_SCORE = 20;
+    public static final int MAX_FIELD_SCORE = 30;
+    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+    private static final class Pair<U,V> {
+        private final U u;
+        private final V v;
+        public Pair(U u, V v){
+            this.u = u;
+            this.v = v;
+        }
+    }
+    
+    /**
+     * Returns a string detailing possible solutions to a missing method
+     * if no good solutions can be found a empty string is returned.
+     *
+     * @param methodName the name of the method that doesn't exist
+     * @param type the class on which the method is invoked
+     * @param arguments the arguments passed to the method
+     * @return a string with probable solutions to the exception
+     */
+    public static String getMethodSuggestionString(String methodName, Class type, Object[] arguments){
+        ClassInfo ci = ClassInfo.getClassInfo(type);
+        List<MetaMethod> methods = new ArrayList<MetaMethod>(ci.getMetaClass().getMethods());
+        methods.addAll(ci.getMetaClass().getMetaMethods());
+        List<MetaMethod> sugg = rankMethods(methodName,arguments,methods);
+        StringBuilder sb = new StringBuilder();
+        if (!sugg.isEmpty()){
+            sb.append("\nPossible solutions: ");
+            for(int i = 0; i < sugg.size(); i++) {
+                if(i != 0) sb.append(", ");
+                sb.append(sugg.get(i).getName()).append("(");
+                sb.append(listParameterNames(sugg.get(i).getParameterTypes()));
+                sb.append(")");
+            }
+        }
+        Class[] argumentClasses = getArgumentClasses(arguments);
+        List<Pair<Class,Class>> conflictClasses = getConflictClasses(sugg,argumentClasses);
+        if (!conflictClasses.isEmpty()){
+            sb.append("\nThe following classes appear as argument class and as parameter class, ");
+            sb.append("but are defined by different class loader:\n");
+            boolean first = true;
+            for(Pair<Class,Class> pair: conflictClasses) {
+                if (!first) {
+                    sb.append(", ");
+                } else {
+                    first = false;
+                }
+                sb.append(pair.u.getName()).append(" (defined by '");
+                sb.append(pair.u.getClassLoader());
+                sb.append("' and '");
+                sb.append(pair.v.getClassLoader());
+                sb.append("')");
+            }
+            sb.append("\nIf one of the method suggestions matches the method you wanted to call, ");
+            sb.append("\nthen check your class loader setup.");
+        }
+        return sb.toString();
+    }
+    
+    private static List<Pair<Class,Class>> getConflictClasses(List<MetaMethod> sugg, Class[] argumentClasses) {
+        List<Pair<Class,Class>> ret = new LinkedList<Pair<Class,Class>>();
+        Set<Class> recordedClasses = new HashSet<Class>();
+        for (MetaMethod method : sugg) {
+            Class[] para = method.getNativeParameterTypes();
+            for (Class aPara : para) {
+                if (recordedClasses.contains(aPara)) continue;
+                for (Class argumentClass : argumentClasses) {
+                    if (argumentClass == null) continue;
+                    if (argumentClass == aPara) continue;
+                    if (argumentClass.getName().equals(aPara.getName())) {
+                        ret.add(new Pair<Class, Class>(argumentClass, aPara));
+                    }
+                }
+                recordedClasses.add(aPara);
+            }
+        }
+        return ret;
+    }
+
+    private static Class[] getArgumentClasses(Object[] arguments) {
+        Class[] argumentClasses = new Class[arguments.length];
+        for (int i=0; i<argumentClasses.length; i++) {
+            Object arg = arguments[i];
+            if (arg==null) continue;
+            argumentClasses[i] = arg.getClass();
+        }
+        return argumentClasses;
+    }
+
+    /**
+     * Returns a string detailing possible solutions to a missing constructor
+     * if no good solutions can be found a empty string is returned.
+     *
+     * @param arguments the arguments passed to the constructor
+     * @param type the class on which the constructor is invoked
+     * @return a string with probable solutions to the exception
+     */
+    public static String getConstructorSuggestionString(Class type, Object[] arguments){
+        Constructor[] sugg = rankConstructors(arguments, type.getConstructors());
+        if(sugg.length >0){
+            StringBuilder sb = new StringBuilder();
+            sb.append("\nPossible solutions: ");
+            for(int i = 0; i < sugg.length; i++){
+                if(i != 0) sb.append(", ");
+                sb.append(type.getName()).append("(");
+                sb.append(listParameterNames(sugg[i].getParameterTypes()));
+                sb.append(")");
+            }
+            return sb.toString();
+        } else{
+            return "";
+        }
+    }
+    
+    /**
+     * Returns a string detailing possible solutions to a missing field or property
+     * if no good solutions can be found a empty string is returned.
+     *
+     * @param fieldName the missing field
+     * @param type the class on which the field is sought
+     * @return a string with probable solutions to the exception
+     */
+    public static String getPropertySuggestionString(String fieldName, Class type){
+        ClassInfo ci = ClassInfo.getClassInfo(type);
+        List<MetaProperty>  fi = ci.getMetaClass().getProperties();
+        List<RankableField> rf = new ArrayList<RankableField>(fi.size());
+        StringBuilder sb = new StringBuilder();
+        sb.append("\nPossible solutions: ");
+        
+        for(MetaProperty mp : fi) rf.add(new RankableField(fieldName, mp));
+        Collections.sort(rf);
+
+        int i = 0;
+        for (RankableField f : rf) {
+            if (i > MAX_RECOMENDATIONS) break;
+            if (f.score > MAX_FIELD_SCORE) break;
+            if(i > 0) sb.append(", ");
+            sb.append(f.f.getName());
+            i++;
+        }
+        return i > 0? sb.toString(): "";
+    }
+    
+    /**
+     * creates a comma separated list of each of the class names.
+     *
+     * @param cachedClasses the array of Classes
+     * @return the Class names
+     */
+    private static String listParameterNames(Class[] cachedClasses){
+        StringBuilder sb = new StringBuilder();
+      for(int i =0; i < cachedClasses.length;i++){
+          if(i != 0) sb.append(", ");
+          sb.append(cachedClasses[i].getName());
+      }
+      return sb.toString();
+    }
+    
+    
+    private static String listParameterNames(CachedClass[] cachedClasses){
+        StringBuilder sb = new StringBuilder();
+        for(int i =0; i < cachedClasses.length;i++){
+            if(i != 0) sb.append(", ");
+            sb.append(cachedClasses[i].getName());
+        }
+        return sb.toString();
+      }
+    
+    /**
+     * Returns a sorted(ranked) list of a selection of the methods among candidates which
+     * most closely resembles original.
+     *
+     * @param name
+     * @param original
+     * @param methods
+     * @return a sorted lists of Methods
+     */
+    private static List<MetaMethod> rankMethods(String name, Object[] original, List<MetaMethod> methods) {
+        List<RankableMethod> rm = new ArrayList<RankableMethod>(methods.size());
+        if (original==null) original = EMPTY_OBJECT_ARRAY;
+        Class[] ta = new Class[original.length];
+    
+        Class nullC =  NullObject.class;
+        for(int i = 0; i < original.length; i++){
+            //All nulls have to be wrapped so that they can be compared
+            ta[i] = original[i] == null?nullC: original[i].getClass();
+        }
+
+        for (MetaMethod m:methods) {
+            rm.add(new RankableMethod(name, ta, m));
+        }
+        Collections.sort(rm);
+        
+        List<MetaMethod> l =  new ArrayList<MetaMethod>(rm.size());
+        for (RankableMethod m : rm) {
+            if (l.size() > MAX_RECOMENDATIONS) break;
+            if (m.score > MAX_METHOD_SCORE) break;
+            l.add(m.m);
+        }
+        return l;
+    }
+
+    /**
+     * This class wraps a method object and a score variable so methods 
+     * Can easily be ranked by their likeness to a another method
+     *
+     */
+    private static final class RankableMethod implements Comparable {
+        final MetaMethod m;
+        final Integer score;
+
+        public RankableMethod(String name, Class[] argumentTypes, MetaMethod m2) {
+            this.m = m2;
+            int nameDist = delDistance(name, m2.getName());
+
+            //unbox primitives
+            Class[] mArgs = new Class[m2.getParameterTypes().length];
+            for(int i =0; i < mArgs.length; i++){
+                //All args have to be boxed since argumentTypes is always boxed
+                mArgs[i] = boxVar(m2.getParameterTypes()[i].getTheClass());
+            }
+            int argDist = damerauLevenshteinDistance(argumentTypes,mArgs);
+            this.score = nameDist + argDist;
+        }
+
+        public int compareTo(Object o) {
+            RankableMethod mo = (RankableMethod) o;
+            return score.compareTo(mo.score);
+        }
+    }
+
+    /**
+     * Returns a sorted(ranked) list of a selection of the constructors among candidates which
+     * most closely resembles original.
+     *
+     * @param original
+     * @param candidates
+     * @return a sorted lists of Methods
+     */
+    private static Constructor[] rankConstructors(Object[] original, Constructor[] candidates) {
+        RankableConstructor[] rc = new RankableConstructor[candidates.length];
+        Class[] ta = new Class[original.length];
+
+        Class nullC = NullObject.class;
+        for (int i = 0; i < original.length; i++) {
+            //All nulls have to be wrapped so that they can be compared
+            ta[i] = original[i] == null ? nullC : original[i].getClass();
+        }
+
+        for (int i = 0; i < candidates.length; i++) {
+            rc[i] = new RankableConstructor(ta, candidates[i]);
+        }
+        Arrays.sort(rc);
+        List<Constructor> l = new ArrayList<Constructor>();
+        int index = 0;
+        while (l.size() < MAX_RECOMENDATIONS && index < rc.length && rc[index].score < MAX_CONSTRUCTOR_SCORE) {
+            l.add(rc[index].c);
+            index++;
+        }
+        return l.toArray(new Constructor[l.size()]);
+    }
+
+    /**
+     * This class wraps a method object and a score variable so methods 
+     * Can easily be ranked by their likeness to a another method
+     *
+     */
+    private static final class RankableConstructor implements Comparable {
+        final Constructor c;
+        final Integer score;
+
+        public RankableConstructor(Class[] argumentTypes, Constructor c) {
+            this.c = c;
+            //unbox primitives
+            Class[] cArgs = new Class[c.getParameterTypes().length];
+            for(int i =0; i < cArgs.length; i++){
+                //All args have to be boxed since argumentTypes is always boxed
+                cArgs[i] = boxVar(c.getParameterTypes()[i]);
+            }
+
+            this.score = damerauLevenshteinDistance(argumentTypes,cArgs);
+        }
+
+        public int compareTo(Object o) {
+            RankableConstructor co = (RankableConstructor) o;
+            return score.compareTo(co.score);
+        }
+    }
+    
+    /**
+     * This class wraps a method object and a score variable so methods 
+     * Can easily be ranked by their likeness to a another method
+     *
+     */
+    private static final class RankableField implements Comparable {
+        final MetaProperty f;
+        final Integer score;
+
+        public RankableField(String name, MetaProperty mp) {
+            this.f = mp;
+            this.score = delDistance(name,mp.getName());
+        }
+
+        public int compareTo(Object o) {
+            RankableField co = (RankableField) o;
+            return score.compareTo(co.score);
+        }
+    }
+    
+    /**
+     * If c is a primitive class this method returns a boxed version
+     * otherwise c is returned.
+     * In java 1.5 this can be simplified thanks to the Type class.
+     * @param c
+     * @return a boxed version of c if c can be boxed, else c
+     */
+    protected static Class boxVar(Class c){
+        if(Boolean.TYPE.equals(c)){
+            return Boolean.class;
+        }else if(Character.TYPE.equals(c)){
+            return Character.class;
+        }else if(Byte.TYPE.equals(c)){
+            return Byte.class;
+        }else if(Double.TYPE.equals(c)){
+            return Double.class;
+        }else if(Float.TYPE.equals(c)){
+            return Float.class;
+        }else if(Integer.TYPE.equals(c)){
+            return Integer.class;
+        }else if(Long.TYPE.equals(c)){
+            return Long.class;
+        }else if(Short.TYPE.equals(c)){
+            return Short.class;
+        }else{
+            return c;
+        }
+    }
+    
+    /**
+     * This is a small wrapper for nulls
+     */
+    private static class NullObject{
+    }
+
+    /**
+     * This is a slightly modified version of the Damerau Levenshtein distance
+     * algorithm. It has a additional test to see if a character has switched case,
+     * in the original algorithm this counts as a substitution.
+     * The "cost" for a substitution is given as 10 instead of 1 in this version,
+     * this enables transpositions and case modifications to have a lower cost than
+     * substitutions.
+     *
+     * Currently the lowercase versions of t_j and s_i isn't cached, its probable
+     * that some speed could be gained from this.
+     * 
+     * This version is based on Chas Emerick's implementation of Levenshtein Distance
+     * for jakarta commons.
+     * @param s a CharSequence
+     * @param t the CharSequence to be compared to s
+     * @return a value representing the edit distance between s and t
+     */
+    public static int delDistance(CharSequence s, CharSequence t) {
+        if (s == null || t == null) {
+            throw new IllegalArgumentException("Strings must not be null");
+        }
+
+        int n = s.length(); // length of s
+        int m = t.length(); // length of t
+
+        if (n == 0) {
+            return m;
+        } else if (m == 0) {
+            return n;
+        }
+
+        //we have to keep 3 rows instead of the 2 used in Levenshtein
+        int[][] vals = new int[3][n + 1];
+
+
+        int _d[]; //placeholder to assist in rotating vals
+
+        // indexes into strings s and t
+        int i; // iterates through s
+        int j; // iterates through t
+
+        char t_j; // jth character of t
+        char s_i; // ith character of s
+        int cost; // cost
+
+        for (i = 0; i <= n; i++) {
+            vals[1][i] = i * DL_DELETE;
+        }
+
+        for (j = 1; j <= m; j++) {
+            t_j = t.charAt(j - 1);
+            vals[0][0] = j * DL_DELETE;
+
+            for (i = 1; i <= n; i++) {
+                s_i = s.charAt(i - 1);
+                if (Character.isLowerCase(s_i) ^ Character.isLowerCase(t_j)) {
+                    //if s_i and t_i don't have have the same case
+                    cost = caselessCompare(s_i, t_j) ? DL_CASE : DL_SUBSTITUTION;
+                } else {
+                    //if they share case check for substitution
+                    cost = s_i == t_j ? 0 : DL_SUBSTITUTION;
+                }
+
+                // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
+                vals[0][i] = Math.min(Math.min(vals[0][i - 1] + DL_DELETE, vals[1][i] + DL_DELETE), vals[1][i - 1] + cost);
+
+                //Check for transposition, somewhat more complex now since we have to check for case
+                if (i > 1 && j > 1) {
+                    cost = Character.isLowerCase(s_i) ^ Character.isLowerCase(t.charAt(j - 2)) ? DL_CASE : 0;
+                    cost = Character.isLowerCase(s.charAt(i - 2)) ^ Character.isLowerCase(t_j) ? cost + DL_CASE : cost;
+
+                    if (caselessCompare(s_i, t.charAt(j - 2)) && caselessCompare(s.charAt(i - 2), t_j)) {
+                        vals[0][i] = Math.min(vals[0][i], vals[2][i - 2] + DL_TRANSPOSITION + cost);
+                    }
+                }
+            }
+
+            // rotate all value arrays upwards(older rows get a higher index)
+            _d = vals[2];
+            vals[2] = vals[1];
+            vals[1] = vals[0];
+            vals[0] = _d;
+        }
+
+        // our last action in the above loop was to rotate vals, so vals[1] now
+        // actually has the most recent cost counts
+        return vals[1][n];
+    }
+
+    /**
+     * Compares two characters whilst ignoring case.
+     * @param a the first character
+     * @param b the second character
+     * @return true if the characters are equal
+     */
+    private static boolean caselessCompare(char a, char b){
+        return Character.toLowerCase(a) == Character.toLowerCase(b);
+    }
+    
+    /**
+     * This is a implementation of DL distance between two Object arrays instead
+     * of character streams. The objects are compared using their equals method.
+     * No objects may be null.
+     * This implementation is based on Chas Emerick's implementation of Levenshtein Distance
+     * for jakarta commons.
+     * @param s an Object array
+     * @param t this array is compared to s
+     * @return the edit distance between the two arrays
+     */
+    public static int damerauLevenshteinDistance(Object[] s, Object[] t) {
+        if (s == null || t == null) {
+            throw new IllegalArgumentException("Arrays must not be null");
+        }
+
+        int n = s.length; // length of s
+        int m = t.length; // length of t
+
+        if (n == 0) {
+            return m;
+        } else if (m == 0) {
+            return n;
+        }
+
+        int[][] vals = new int[3][n + 1];
+
+
+        int _d[]; //placeholder to assist in rotating vals
+
+        // indexes into arrays s and t
+        int i; // iterates through s
+        int j; // iterates through t
+
+        Object t_j; // jth object of t
+
+        int cost; // cost
+
+        for (i = 0; i <= n; i++) {
+            vals[1][i] = i * DL_DELETE ;
+        }
+
+        for (j = 1; j <= m; j++) {
+            t_j = t[j - 1];
+            vals[0][0] = j * DL_DELETE ;
+
+            for (i = 1; i <= n; i++) {
+                cost = s[i - 1].equals(t_j)? 0 : DL_SUBSTITUTION;
+                // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
+                vals[0][i] = Math.min(Math.min(vals[0][i - 1] + DL_DELETE, vals[1][i] + DL_DELETE), vals[1][i - 1] + cost);
+
+                //Check for transposition
+                if(i > 1 && j > 1 && s[i -1].equals(t[j -2]) && s[i- 2].equals(t_j)){
+                    vals[0][i] = Math.min(vals[0][i], vals[2][i-2] + DL_TRANSPOSITION);
+                }
+            }
+
+            // rotate all value arrays upwards(older rows get a higher index)
+            _d = vals[2];
+            vals[2] = vals[1];
+            vals[1] = vals[0];
+            vals[0] = _d;
+        }
+
+        return vals[1][n];
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/NullObject.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/NullObject.java b/src/main/java/org/codehaus/groovy/runtime/NullObject.java
new file mode 100644
index 0000000..48f9170
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/NullObject.java
@@ -0,0 +1,176 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.runtime;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyObjectSupport;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+public class NullObject extends GroovyObjectSupport {
+    private static final NullObject INSTANCE = new NullObject();
+
+    /**
+     * private constructor
+     */
+    private NullObject() {
+    }
+
+    /**
+     * get the NullObject reference
+     *
+     * @return the null object
+     */
+    public static NullObject getNullObject() {
+        return INSTANCE;
+    }
+
+    /**
+     * Since this is implemented as a singleton, we should avoid the
+     * use of the clone method
+     */
+    public Object clone() {
+        throw new NullPointerException("Cannot invoke method clone() on null object");
+    }
+
+    /**
+     * Tries to get a property on null, which will always fail
+     *
+     * @param property - the property to get
+     * @return a NPE
+     */
+    public Object getProperty(String property) {
+        throw new NullPointerException("Cannot get property '" + property + "' on null object");
+    }
+
+    /**
+     * Allows the closure to be called for NullObject
+     *
+     * @param closure the closure to call on the object
+     * @return result of calling the closure
+     */
+    public <T> T with( Closure<T> closure ) {
+        return DefaultGroovyMethods.with( null, closure ) ;
+    }
+
+    /**
+     * Tries to set a property on null, which will always fail
+     *
+     * @param property - the proprty to set
+     * @param newValue - the new value of the property
+     */
+    public void setProperty(String property, Object newValue) {
+        throw new NullPointerException("Cannot set property '" + property + "' on null object");
+    }
+
+    /**
+     * Tries to invoke a method on null, which will always fail
+     *
+     * @param name the name of the method to invoke
+     * @param args - arguments to the method
+     * @return a NPE
+     */
+    public Object invokeMethod(String name, Object args) {
+        throw new NullPointerException("Cannot invoke method " + name + "() on null object");
+    }
+
+    /**
+     * null is only equal to null
+     *
+     * @param to - the reference object with which to compare
+     * @return - true if this object is the same as the to argument
+     */
+    public boolean equals(Object to) {
+        return to == null;
+    }
+
+    /**
+     * iterator() method to be able to iterate on null.
+     * Note: this part is from Invoker
+     *
+     * @return an iterator for an empty list
+     */
+    public Iterator iterator() {
+        return Collections.EMPTY_LIST.iterator();
+    }
+
+    /**
+     * Allows to add a String to null.
+     * The result is concatenated String of the result of calling
+     * toString() on this object and the String in the parameter.
+     *
+     * @param s - the String to concatenate
+     * @return the concatenated string
+     */
+    public Object plus(String s) {
+        return getMetaClass().invokeMethod(this, "toString", new Object[]{}) + s;
+    }
+
+    /**
+     * Fallback for null+null.
+     * The result is always a NPE. The plus(String) version will catch 
+     * the case of adding a non null String to null.
+     *
+     * @param o - the Object
+     * @return nothing
+     */
+    public Object plus(Object o) {
+        throw new NullPointerException("Cannot execute null+" + String.valueOf(o));
+    }
+
+    /**
+     * The method "is" is used to test for equal references.
+     * This method will return true only if the given parameter
+     * is null
+     *
+     * @param other - the object to test
+     * @return true if other is null
+     */
+    public boolean is(Object other) {
+        return other == null;
+    }
+
+    /**
+     * Type conversion method for null.
+     *
+     * @param c - the class to convert to
+     * @return always null
+     */
+    public Object asType(Class c) {
+        return null;
+    }
+
+    /**
+     * A null object always coerces to false.
+     * 
+     * @return false
+     */
+    public boolean asBoolean() {
+        return false;
+    }
+
+    public String toString() {
+        return "null";
+    }
+
+    public int hashCode() {
+        throw new NullPointerException("Cannot invoke method hashCode() on null object");
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/a188738d/src/main/java/org/codehaus/groovy/runtime/NumberAwareComparator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/NumberAwareComparator.java b/src/main/java/org/codehaus/groovy/runtime/NumberAwareComparator.java
new file mode 100644
index 0000000..ee4c8c4
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/runtime/NumberAwareComparator.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.runtime;
+
+import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * Compares two objects using Groovy's friendly comparison algorithm, i.e.
+ * handles nulls gracefully (nul being less than everything else) and
+ * performs numeric type coercion if required.
+ */
+public class NumberAwareComparator<T> implements Comparator<T>, Serializable {
+    private static final long serialVersionUID = 9017657289076651660L;
+
+    public int compare(T o1, T o2) {
+        try {
+            return DefaultTypeTransformation.compareTo(o1, o2);
+        } catch (ClassCastException cce) {
+            /* ignore */
+        } catch (GroovyRuntimeException gre) {
+            /* ignore */
+        } catch (IllegalArgumentException iae) {
+            /* ignore */
+        }
+        // since the object does not have a valid compareTo method
+        // we compare using the hashcodes. null cases are handled by
+        // DefaultTypeTransformation.compareTo
+        // This is not exactly a mathematical valid approach, since we compare object
+        // that cannot be compared. To avoid strange side effects we do a pseudo order
+        // using hashcodes, but without equality. Since then an x and y with the same
+        // hashcodes will behave different depending on if we compare x with y or
+        // x with y, the result might be unstable as well. Setting x and y to equal
+        // may mean the removal of x or y in a sorting operation, which we don't want.
+        int x1 = o1.hashCode();
+        int x2 = o2.hashCode();
+        if (x1 == x2 && o1.equals(o2)) return 0;
+        if (x1 > x2) return 1;
+        return -1;
+    }
+}


Mime
View raw message