http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java 
b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
new file mode 100644
index 0000000..1489a56
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
@@ -0,0 +1,746 @@
+/*
+ *  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.ast.tools;
+
+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 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.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.Number_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.char_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.double_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.float_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
+import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
+import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.isNumberType;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.ClassHelper.long_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
+
+/**
+ * This class provides helper methods to determine the type from a widening
+ * operation for example for a plus operation.
+ * <p>
+ * To determine the resulting type of for example a=exp1+exp2 we look at the
+ * conditions {@link #isIntCategory(ClassNode)}, {@link 
#isLongCategory(ClassNode)},
+ * {@link #isBigIntCategory(ClassNode)}, {@link #isDoubleCategory(ClassNode)} 
and
+ * {@link #isBigDecCategory(ClassNode)} in that order. The first case applying 
to
+ * exp1 and exp2 is defining the result type of the expression.
+ * <p>
+ * If for example you look at x = 1 + 2l we have the first category applying to
+ * the number 1 being int, since the 1 is an int. The 2l is a long, therefore 
the
+ * int category will not apply and the result type can't be int. The next 
category
+ * in the list is long, and since both apply to long, the result type is a 
long.
+ *
+ * @author <a href="mailto:[email protected]";>Jochen "blackdrag" Theodorou</a>
+ * @author Cedric Champeau
+ */
+public class WideningCategories {
+
+    private static final List<ClassNode> EMPTY_CLASSNODE_LIST = 
Collections.emptyList();
+
+    private static final Map<ClassNode, Integer> NUMBER_TYPES_PRECEDENCE = 
Collections.unmodifiableMap(new HashMap<ClassNode, Integer>() {{
+        put(ClassHelper.double_TYPE, 0);
+        put(ClassHelper.float_TYPE, 1);
+        put(ClassHelper.long_TYPE, 2);
+        put(ClassHelper.int_TYPE, 3);
+        put(ClassHelper.short_TYPE, 4);
+        put(ClassHelper.byte_TYPE, 5);
+    }});
+
+    /**
+     * A comparator which is used in case we generate a virtual lower upper 
bound class node. In that case,
+     * since a concrete implementation should be used at compile time, we must 
ensure that interfaces are
+     * always sorted. It is not important what sort is used, as long as the 
result is constant.
+     */
+    private static final Comparator<ClassNode> INTERFACE_CLASSNODE_COMPARATOR 
= new Comparator<ClassNode>() {
+        public int compare(final ClassNode o1, final ClassNode o2) {
+            int interfaceCountForO1 = o1.getInterfaces().length;
+            int interfaceCountForO2 = o2.getInterfaces().length;
+            if (interfaceCountForO1 > interfaceCountForO2) return -1;
+            if (interfaceCountForO1 < interfaceCountForO2) return 1;
+            int methodCountForO1 = o1.getMethods().size();
+            int methodCountForO2 = o2.getMethods().size();
+            if (methodCountForO1 > methodCountForO2) return -1;
+            if (methodCountForO1 < methodCountForO2) return 1;
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
+    /**
+     * Used to check if a type is an int or Integer.
+     * @param type the type to check
+     */
+    public static boolean isInt(ClassNode type) {
+        return int_TYPE == type;
+    }
+
+    /**
+     * Used to check if a type is an double or Double.
+     * @param type the type to check
+     */
+    public static boolean isDouble(ClassNode type) {
+        return double_TYPE == type;
+    }
+
+    /**
+     * Used to check if a type is a float or Float.
+     * @param type the type to check
+     */
+    public static boolean isFloat(ClassNode type) {
+        return float_TYPE == type;
+    }
+
+    /**
+     * It is of an int category, if the provided type is a
+     * byte, char, short, int.
+     */
+    public static boolean isIntCategory(ClassNode type) {
+        return  type==byte_TYPE     ||  type==char_TYPE     ||
+                type==int_TYPE      ||  type==short_TYPE;
+    }
+    /**
+     * It is of a long category, if the provided type is a
+     * long, its wrapper or if it is a long category. 
+     */
+    public static boolean isLongCategory(ClassNode type) {
+        return  type==long_TYPE     ||  isIntCategory(type);
+    }
+    /**
+     * It is of a BigInteger category, if the provided type is a
+     * long category or a BigInteger. 
+     */
+    public static boolean isBigIntCategory(ClassNode type) {
+        return  type==BigInteger_TYPE || isLongCategory(type);
+    }
+    /**
+     * It is of a BigDecimal category, if the provided type is a
+     * BigInteger category or a BigDecimal. 
+     */
+    public static boolean isBigDecCategory(ClassNode type) {
+        return  type==BigDecimal_TYPE || isBigIntCategory(type);
+    }
+    /**
+     * It is of a double category, if the provided type is a
+     * BigDecimal, a float, double. C(type)=double
+     */
+    public static boolean isDoubleCategory(ClassNode type) {
+        return  type==float_TYPE    ||  type==double_TYPE   ||
+                isBigDecCategory(type);
+    }
+
+    /**
+     * It is of a floating category, if the provided type is a
+     * a float, double. C(type)=float
+     */
+    public static boolean isFloatingCategory(ClassNode type) {
+        return  type==float_TYPE    ||  type==double_TYPE;
+    }
+
+    public static boolean isNumberCategory(ClassNode type) {
+        return isBigDecCategory(type) || type.isDerivedFrom(Number_TYPE);
+    }
+
+    /**
+     * Given a list of class nodes, returns the first common supertype.
+     * For example, Double and Float would return Number, while
+     * Set and String would return Object.
+     * @param nodes the list of nodes for which to find the first common super 
type.
+     * @return first common supertype
+     */
+    public static ClassNode lowestUpperBound(List<ClassNode> nodes) {
+        if (nodes.size()==1) return nodes.get(0);
+        return lowestUpperBound(nodes.get(0), 
lowestUpperBound(nodes.subList(1, nodes.size())));
+    }
+
+    /**
+     * Given two class nodes, returns the first common supertype, or the class 
itself
+     * if there are equal. For example, Double and Float would return Number, 
while
+     * Set and String would return Object.
+     *
+     * This method is not guaranteed to return a class node which corresponds 
to a
+     * real type. For example, if two types have more than one interface in 
common
+     * and are not in the same hierarchy branch, then the returned type will 
be a
+     * virtual type implementing all those interfaces.
+     *
+     * Calls to this method are supposed to be made with resolved generics. 
This means
+     * that you can have wildcards, but no placeholder.
+     *
+     * @param a first class node
+     * @param b second class node
+     * @return first common supertype
+     */
+    public static ClassNode lowestUpperBound(ClassNode a, ClassNode b) {
+        ClassNode lub = lowestUpperBound(a, b, null, null);
+        if (lub==null || !lub.isUsingGenerics()) return lub;
+        // types may be parameterized. If so, we must ensure that generic type 
arguments
+        // are made compatible
+
+        if (lub instanceof LowestUpperBoundClassNode) {
+            // no parent super class representing both types could be found
+            // or both class nodes implement common interfaces which may have
+            // been parameterized differently.
+            // We must create a classnode for which the "superclass" is 
potentially parameterized
+            // plus the interfaces
+            ClassNode superClass = lub.getSuperClass();
+            ClassNode psc = 
superClass.isUsingGenerics()?parameterizeLowestUpperBound(superClass, a, b, 
lub):superClass;
+
+            ClassNode[] interfaces = lub.getInterfaces();
+            ClassNode[] pinterfaces = new ClassNode[interfaces.length];
+            for (int i = 0, interfacesLength = interfaces.length; i < 
interfacesLength; i++) {
+                final ClassNode icn = interfaces[i];
+                if (icn.isUsingGenerics()) {
+                    pinterfaces[i] = parameterizeLowestUpperBound(icn, a, b, 
lub);
+                } else {
+                    pinterfaces[i] = icn;
+                }
+            }
+
+            return new 
LowestUpperBoundClassNode(((LowestUpperBoundClassNode)lub).name, psc, 
pinterfaces);
+        } else {
+            return parameterizeLowestUpperBound(lub, a, b, lub);
+
+        }
+    }
+
+    /**
+     * Given a lowest upper bound computed without generic type information 
but which requires to be parameterized
+     * and the two implementing classnodes which are parameterized with 
potentially two different types, returns
+     * a parameterized lowest upper bound.
+     *
+     * For example, if LUB is Set&lt;T&gt; and a is Set&lt;String&gt; and b is 
Set&lt;StringBuffer&gt;, this
+     * will return a LUB which parameterized type matches Set&lt;? extends 
CharSequence&gt;
+     * @param lub the type to be parameterized
+     * @param a parameterized type a
+     * @param b parameterized type b
+     * @param fallback if we detect a recursive call, use this LUB as the 
parameterized type instead of computing a value
+     * @return the class node representing the parameterized lowest upper bound
+     */
+    private static ClassNode parameterizeLowestUpperBound(final ClassNode lub, 
final ClassNode a, final ClassNode b, final ClassNode fallback) {
+        if (!lub.isUsingGenerics()) return lub;
+        // a common super type exists, all we have to do is to parameterize
+        // it according to the types provided by the two class nodes
+        ClassNode holderForA = findGenericsTypeHolderForClass(a, lub);
+        ClassNode holderForB = findGenericsTypeHolderForClass(b, lub);
+        // let's compare their generics type
+        GenericsType[] agt = holderForA == null ? null : 
holderForA.getGenericsTypes();
+        GenericsType[] bgt = holderForB == null ? null : 
holderForB.getGenericsTypes();
+        if (agt==null || bgt==null || agt.length!=bgt.length) {
+            return lub;
+        }
+        GenericsType[] lubgt = new GenericsType[agt.length];
+        for (int i = 0; i < agt.length; i++) {
+            ClassNode t1 = agt[i].getType();
+            ClassNode t2 = bgt[i].getType();
+            ClassNode basicType;
+            if (areEqualWithGenerics(t1, a) && areEqualWithGenerics(t2,b)) {
+                // we are facing a self referencing type !
+                basicType = fallback;
+            } else {
+                 basicType = lowestUpperBound(t1, t2);
+            }
+            if (t1.equals(t2)) {
+                lubgt[i] = new GenericsType(basicType);
+            } else {
+                lubgt[i] = GenericsUtils.buildWildcardType(basicType);
+            }
+        }
+        ClassNode plain = lub.getPlainNodeReference();
+        plain.setGenericsTypes(lubgt);
+        return plain;
+    }
+
+    private static ClassNode findGenericsTypeHolderForClass(ClassNode source, 
ClassNode type) {
+        if (isPrimitiveType(source)) source = getWrapper(source);
+        if (source.equals(type)) return source;
+        if (type.isInterface()) {
+            for (ClassNode interfaceNode : source.getAllInterfaces()) {
+                if (interfaceNode.equals(type)) {
+                    ClassNode parameterizedInterface = 
GenericsUtils.parameterizeType(source, interfaceNode);
+                    return parameterizedInterface;
+                }
+            }
+        }
+        ClassNode superClass = source.getUnresolvedSuperClass();
+        // copy generic type information if available
+        if (superClass!=null && superClass.isUsingGenerics()) {
+            Map<String, GenericsType> genericsTypeMap = 
GenericsUtils.extractPlaceholders(source);
+            GenericsType[] genericsTypes = superClass.getGenericsTypes();
+            if (genericsTypes!=null) {
+                GenericsType[] copyTypes = new 
GenericsType[genericsTypes.length];
+                for (int i = 0; i < genericsTypes.length; i++) {
+                    GenericsType genericsType = genericsTypes[i];
+                    if (genericsType.isPlaceholder() && 
genericsTypeMap.containsKey(genericsType.getName())) {
+                        copyTypes[i] = 
genericsTypeMap.get(genericsType.getName());
+                    } else {
+                        copyTypes[i] = genericsType;
+                    }
+                }
+                superClass = superClass.getPlainNodeReference();
+                superClass.setGenericsTypes(copyTypes);
+            }
+        }
+        if (superClass!=null) return 
findGenericsTypeHolderForClass(superClass, type);
+        return null;
+    }
+
+    private static ClassNode lowestUpperBound(ClassNode a, ClassNode b, 
List<ClassNode> interfacesImplementedByA, List<ClassNode> 
interfacesImplementedByB) {
+        // first test special cases
+        if (a==null || b==null) {
+            // this is a corner case, you should not
+            // compare two class nodes if one of them is null
+            return null;
+        }
+        if (a.isArray() && b.isArray()) {
+            return lowestUpperBound(a.getComponentType(), 
b.getComponentType(), interfacesImplementedByA, 
interfacesImplementedByB).makeArray();
+        }
+        if (a.equals(OBJECT_TYPE) || b.equals(OBJECT_TYPE)) {
+            // one of the objects is at the top of the hierarchy
+            GenericsType[] gta = a.getGenericsTypes();
+            GenericsType[] gtb = b.getGenericsTypes();
+            if (gta !=null && gtb !=null && gta.length==1 && gtb.length==1) {
+                if (gta[0].getName().equals(gtb[0].getName())) {
+                    return a;
+                }
+            }
+            return OBJECT_TYPE;
+        }
+        if (a.equals(VOID_TYPE) || b.equals(VOID_TYPE)) {
+            if (!b.equals(a)) {
+                // one class is void, the other is not
+                return OBJECT_TYPE;
+            }
+            return VOID_TYPE;
+        }
+
+        // now handle primitive types
+        boolean isPrimitiveA = isPrimitiveType(a);
+        boolean isPrimitiveB = isPrimitiveType(b);
+        if (isPrimitiveA && !isPrimitiveB) {
+            return lowestUpperBound(getWrapper(a), b, null, null);
+        }
+        if (isPrimitiveB && !isPrimitiveA) {
+            return lowestUpperBound(a, getWrapper(b), null, null);
+        }
+        if (isPrimitiveA && isPrimitiveB) {
+            Integer pa = NUMBER_TYPES_PRECEDENCE.get(a);
+            Integer pb = NUMBER_TYPES_PRECEDENCE.get(b);
+            if (pa!=null && pb!=null) {
+                if (pa<=pb) return a;
+                return b;
+            }
+            return a.equals(b)?a:lowestUpperBound(getWrapper(a), 
getWrapper(b), null, null);
+        }
+        if (isNumberType(a.redirect()) && isNumberType(b.redirect())) {
+            ClassNode ua = getUnwrapper(a);
+            ClassNode ub = getUnwrapper(b);
+            Integer pa = NUMBER_TYPES_PRECEDENCE.get(ua);
+            Integer pb = NUMBER_TYPES_PRECEDENCE.get(ub);
+            if (pa!=null && pb!=null) {
+                if (pa<=pb) return a;
+                return b;
+            }
+        }
+
+        // handle interfaces
+        boolean isInterfaceA = a.isInterface();
+        boolean isInterfaceB = b.isInterface();
+        if (isInterfaceA && isInterfaceB) {
+            if (a.equals(b)) return a;
+            if (b.implementsInterface(a)) {
+                return a;
+            }
+            if (a.implementsInterface(b)) {
+                return b;
+            }
+            // each interface may have one or more "extends", so we must find 
those
+            // which are common
+            ClassNode[] interfacesFromA = a.getInterfaces();
+            ClassNode[] interfacesFromB = b.getInterfaces();
+            Set<ClassNode> common = new HashSet<ClassNode>();
+            Collections.addAll(common, interfacesFromA);
+            Set<ClassNode> fromB = new HashSet<ClassNode>();
+            Collections.addAll(fromB, interfacesFromB);
+            common.retainAll(fromB);
+
+            if (common.size()==1) {
+                return common.iterator().next();
+            } else if (common.size()>1) {
+                return buildTypeWithInterfaces(a, b, common);
+            }
+
+            // we have two interfaces, but none inherits from the other
+            // so the only possible return type is Object
+            return OBJECT_TYPE;
+        } else if (isInterfaceB) {
+            return lowestUpperBound(b, a, null, null);
+        } else if (isInterfaceA) {
+            // a is an interface, b is not
+
+            // a ClassNode superclass for an interface is not
+            // another interface but always Object. This implies that
+            // "extends" for an interface is understood as "implements"
+            // for a ClassNode. Therefore, even if b doesn't implement
+            // interface a, a could "implement" other interfaces that b
+            // implements too, so we must create a list of matching interfaces
+            List<ClassNode> matchingInterfaces = new LinkedList<ClassNode>();
+            extractMostSpecificImplementedInterfaces(b, a, matchingInterfaces);
+            if (matchingInterfaces.isEmpty()) {
+                // no interface in common
+                return OBJECT_TYPE;
+            }
+            if (matchingInterfaces.size()==1) {
+                // a single match, which should be returned
+                return matchingInterfaces.get(0);
+            }
+            return buildTypeWithInterfaces(a,b, matchingInterfaces);
+        }
+        // both classes do not represent interfaces
+        if (a.equals(b)) {
+            return buildTypeWithInterfaces(a,b, 
keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
+        }
+        // test if one class inherits from the other
+        if (a.isDerivedFrom(b) || b.isDerivedFrom(a)) {
+            return buildTypeWithInterfaces(a,b, 
keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
+        }
+
+        // Look at the super classes
+        ClassNode sa = a.getUnresolvedSuperClass();
+        ClassNode sb = b.getUnresolvedSuperClass();
+
+        // extract implemented interfaces before "going up"
+        Set<ClassNode> ifa = new HashSet<ClassNode>();
+        extractInterfaces(a, ifa);
+        Set<ClassNode> ifb = new HashSet<ClassNode>();
+        extractInterfaces(b, ifb);
+        interfacesImplementedByA = interfacesImplementedByA==null?new 
LinkedList<ClassNode>(ifa):interfacesImplementedByA;
+        interfacesImplementedByB = interfacesImplementedByB==null?new 
LinkedList<ClassNode>(ifb):interfacesImplementedByB;
+
+        // check if no superclass is defined
+        // meaning that we reached the top of the object hierarchy
+        if (sa==null || sb==null) return buildTypeWithInterfaces(OBJECT_TYPE, 
OBJECT_TYPE, keepLowestCommonInterfaces(interfacesImplementedByA, 
interfacesImplementedByB));
+
+
+        // if one superclass is derived (or equals) another
+        // then it is the common super type
+        if (sa.isDerivedFrom(sb) || sb.isDerivedFrom(sa)) {
+            return buildTypeWithInterfaces(sa, sb, 
keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
+        }
+        // superclasses are on distinct hierarchy branches, so we
+        // recurse on them
+        return lowestUpperBound(sa, sb, interfacesImplementedByA, 
interfacesImplementedByB);
+    }
+
+    private static void extractInterfaces(ClassNode node, Set<ClassNode> 
interfaces) {
+        if (node==null) return;
+        Collections.addAll(interfaces, node.getInterfaces());
+        extractInterfaces(node.getSuperClass(), interfaces);
+    }
+    
+    /**
+     * Given the list of interfaces implemented by two class nodes, returns 
the list of the most specific common
+     * implemented interfaces.
+     * @param fromA
+     * @param fromB
+     * @return the list of the most specific common implemented interfaces
+     */
+    private static List<ClassNode> keepLowestCommonInterfaces(List<ClassNode> 
fromA, List<ClassNode> fromB) {
+        if (fromA==null||fromB==null) return EMPTY_CLASSNODE_LIST;
+        Set<ClassNode> common = new HashSet<ClassNode>(fromA);
+        common.retainAll(fromB);
+        List<ClassNode> result = new ArrayList<ClassNode>(common.size());
+        for (ClassNode classNode : common) {
+            addMostSpecificInterface(classNode, result);
+        }
+        return result;
+    }
+
+    private static void addMostSpecificInterface(ClassNode interfaceNode, 
List<ClassNode> nodes) {
+        if (nodes.isEmpty()) nodes.add(interfaceNode);
+        for (int i = 0, nodesSize = nodes.size(); i < nodesSize; i++) {
+            final ClassNode node = nodes.get(i);
+            if 
(node.equals(interfaceNode)||node.implementsInterface(interfaceNode)) {
+                // a more specific interface exists in the list, keep it
+                return;
+            }
+            if (interfaceNode.implementsInterface(node)) {
+                // the interface beeing added is more specific than the one in 
the list, replace it
+                nodes.set(i, interfaceNode);
+                return;
+            }
+        }
+        // if we reach this point, this means the interface is new
+        nodes.add(interfaceNode);
+    }
+
+    private static void extractMostSpecificImplementedInterfaces(final 
ClassNode type, final ClassNode inode, final List<ClassNode> result) {
+        if (type.implementsInterface(inode)) result.add(inode);
+        else {
+            ClassNode[] interfaces = inode.getInterfaces();
+            for (ClassNode interfaceNode : interfaces) {
+                if (type.implementsInterface(interfaceNode)) 
result.add(interfaceNode);
+            }
+            if (result.isEmpty() && interfaces.length>0) {
+                // none if the direct interfaces match, but we must check 
"upper" in the hierarchy
+                for (ClassNode interfaceNode : interfaces) {
+                    extractMostSpecificImplementedInterfaces(type, 
interfaceNode, result);
+                }
+            }
+        }
+    }
+
+    /**
+     * Given two class nodes supposedly at the upper common level, returns a 
class node which is able to represent
+     * their lowest upper bound.
+     * @param baseType1
+     * @param baseType2
+     * @param interfaces interfaces both class nodes share, which their lowest 
common super class do not implement.
+     * @return the class node representing the lowest upper bound
+     */
+    private static ClassNode buildTypeWithInterfaces(ClassNode baseType1, 
ClassNode baseType2, Collection<ClassNode> interfaces) {
+        boolean noInterface = interfaces.isEmpty();
+        if (noInterface) {
+            if (baseType1.equals(baseType2)) return baseType1;
+            if (baseType1.isDerivedFrom(baseType2)) return baseType2;
+            if (baseType2.isDerivedFrom(baseType1)) return baseType1;
+        }
+        if (OBJECT_TYPE.equals(baseType1) && OBJECT_TYPE.equals(baseType2) && 
interfaces.size()==1) {
+            if (interfaces instanceof List) {
+                return ((List<ClassNode>) interfaces).get(0);
+            }
+            return interfaces.iterator().next();
+        }
+        LowestUpperBoundClassNode type;
+        ClassNode superClass;
+        String name;
+        if (baseType1.equals(baseType2)) {
+            if (OBJECT_TYPE.equals(baseType1)) {
+                superClass = baseType1;
+                name = "Virtual$Object";
+            } else {
+                superClass = baseType1;
+                name = "Virtual$"+baseType1.getName();
+            }
+        } else {
+            superClass = OBJECT_TYPE;
+            if (baseType1.isDerivedFrom(baseType2)) {
+                superClass = baseType2;
+            } else if (baseType2.isDerivedFrom(baseType1)) {
+                superClass = baseType1;
+            }
+            name = 
"CommonAssignOf$"+baseType1.getName()+"$"+baseType2.getName();
+        }
+        Iterator<ClassNode> itcn = interfaces.iterator();
+        while (itcn.hasNext()) {
+            ClassNode next = itcn.next();
+            if (superClass.isDerivedFrom(next) || 
superClass.implementsInterface(next)) {
+                itcn.remove();
+            }
+        }
+        ClassNode[] interfaceArray = interfaces.toArray(new 
ClassNode[interfaces.size()]);
+        Arrays.sort(interfaceArray, INTERFACE_CLASSNODE_COMPARATOR);
+        type = new LowestUpperBoundClassNode(name, superClass, interfaceArray);
+        return type;
+    }
+
+    /**
+     * This {@link ClassNode} specialization is used when the lowest upper 
bound of two types
+     * cannot be represented by an existing type. For example, if B extends A, 
 C extends A
+     * and both C & B implement a common interface not implemented by A, then 
we use this class
+     * to represent the bound.
+     *
+     * At compile time, some classes like {@link 
org.codehaus.groovy.classgen.AsmClassGenerator} need
+     * to know about a real class node, so we compute a "compile time" node 
which will be used
+     * to return a name and a type class.
+     *
+     */
+    public static class LowestUpperBoundClassNode extends ClassNode {
+        private static final Comparator<ClassNode> CLASS_NODE_COMPARATOR = new 
Comparator<ClassNode>() {
+            public int compare(final ClassNode o1, final ClassNode o2) {
+                String n1 = o1 instanceof 
LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o1).name:o1.getName();
+                String n2 = o2 instanceof 
LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o2).name:o2.getName();
+                return n1.compareTo(n2);
+            }
+        };
+        private final ClassNode compileTimeClassNode;
+        private final String name;
+        private final String text;
+
+        private final ClassNode upper;
+        private final ClassNode[] interfaces;
+
+        public LowestUpperBoundClassNode(String name, ClassNode upper, 
ClassNode... interfaces) {
+            super(name, ACC_PUBLIC|ACC_FINAL, upper, interfaces, null);
+            this.upper = upper;
+            this.interfaces = interfaces;
+            boolean usesGenerics;
+            Arrays.sort(interfaces, CLASS_NODE_COMPARATOR);
+            compileTimeClassNode = upper.equals(OBJECT_TYPE) && 
interfaces.length>0?interfaces[0]:upper;
+            this.name = name;
+            usesGenerics = upper.isUsingGenerics();
+            List<GenericsType[]> genericsTypesList = new 
LinkedList<GenericsType[]>();
+            genericsTypesList.add(upper.getGenericsTypes());
+                       for (ClassNode anInterface : interfaces) {
+                usesGenerics |= anInterface.isUsingGenerics();
+                genericsTypesList.add(anInterface.getGenericsTypes());
+                               for (MethodNode methodNode : 
anInterface.getMethods()) {
+                    MethodNode method = addMethod(methodNode.getName(), 
methodNode.getModifiers(), methodNode.getReturnType(), 
methodNode.getParameters(), methodNode.getExceptions(), methodNode.getCode());
+                    method.setDeclaringClass(anInterface); // important for 
static compilation!
+                }
+                       }
+            setUsingGenerics(usesGenerics);
+            if (usesGenerics) {
+                List<GenericsType> asArrayList = new ArrayList<GenericsType>();
+                for (GenericsType[] genericsTypes : genericsTypesList) {
+                    if (genericsTypes!=null) {
+                        Collections.addAll(asArrayList, genericsTypes);
+                    }
+                }
+                setGenericsTypes(asArrayList.toArray(new 
GenericsType[asArrayList.size()]));
+            }
+            StringBuilder sb = new StringBuilder();
+            if (!upper.equals(OBJECT_TYPE)) sb.append(upper.getName());
+            for (ClassNode anInterface : interfaces) {
+                if (sb.length()>0) {
+                    sb.append(" or ");
+                }
+                sb.append(anInterface.getName());
+            }
+            this.text = sb.toString();
+        }
+
+        public String getLubName() {
+            return this.name;
+        }
+
+        @Override
+        public String getName() {
+            return compileTimeClassNode.getName();
+        }
+
+        @Override
+        public Class getTypeClass() {
+            return compileTimeClassNode.getTypeClass();
+        }
+
+        @Override
+        public int hashCode() {
+            int result = super.hashCode();
+//            result = 31 * result + (compileTimeClassNode != null ? 
compileTimeClassNode.hashCode() : 0);
+            result = 31 * result + (name != null ? name.hashCode() : 0);
+            return result;
+        }
+
+        @Override
+        public String getText() {
+            return text;
+        }
+
+        @Override
+        public ClassNode getPlainNodeReference() {
+            ClassNode[] intf = interfaces==null?null:new 
ClassNode[interfaces.length];
+            if (intf!=null) {
+                for (int i = 0; i < interfaces.length; i++) {
+                    intf[i] = interfaces[i].getPlainNodeReference();
+                }
+            }
+            LowestUpperBoundClassNode plain = new 
LowestUpperBoundClassNode(name, upper.getPlainNodeReference(), intf);
+            return plain;
+        }
+    }
+
+    /**
+     * Compares two class nodes, but including their generics types.
+     * @param a
+     * @param b
+     * @return true if the class nodes are equal, false otherwise
+     */
+    private static boolean areEqualWithGenerics(ClassNode a, ClassNode b) {
+        if (a==null) return b==null;
+        if (!a.equals(b)) return false;
+        if (a.isUsingGenerics() && !b.isUsingGenerics()) return false;
+        GenericsType[] gta = a.getGenericsTypes();
+        GenericsType[] gtb = b.getGenericsTypes();
+        if (gta==null && gtb!=null) return false;
+        if (gtb==null && gta!=null) return false;
+        if (gta!=null && gtb!=null) {
+            if (gta.length!=gtb.length) return false;
+            for (int i = 0; i < gta.length; i++) {
+                GenericsType ga = gta[i];
+                GenericsType gb = gtb[i];
+                boolean result = ga.isPlaceholder()==gb.isPlaceholder() && 
ga.isWildcard()==gb.isWildcard();
+                result = result && ga.isResolved() && gb.isResolved();
+                result = result && ga.getName().equals(gb.getName());
+                result = result && areEqualWithGenerics(ga.getType(), 
gb.getType());
+                result = result && areEqualWithGenerics(ga.getLowerBound(), 
gb.getLowerBound());
+                if (result) {
+                    ClassNode[] upA = ga.getUpperBounds();
+                    if (upA!=null) {
+                        ClassNode[] upB = gb.getUpperBounds();
+                        if (upB==null || upB.length!=upA.length) return false;
+                        for (int j = 0; j < upA.length; j++) {
+                            if (!areEqualWithGenerics(upA[j],upB[j])) return 
false;
+                        }
+                    }
+                }
+                if (!result) return false;
+            }
+        }
+        return true;
+    }
+    
+    /**
+     * Determines if the source class implements an interface or subclasses 
the target type.
+     * This method takes the {@link 
org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode 
lowest
+     * upper bound class node} type into account, allowing to remove 
unnecessary casts.
+     * @param source the type of interest
+     * @param targetType the target type of interest
+     */
+    public static boolean implementsInterfaceOrSubclassOf(final ClassNode 
source, final ClassNode targetType) {
+        if (source.isDerivedFrom(targetType) || 
source.implementsInterface(targetType)) return true;
+        if (targetType instanceof 
WideningCategories.LowestUpperBoundClassNode) {
+            WideningCategories.LowestUpperBoundClassNode lub = 
(WideningCategories.LowestUpperBoundClassNode) targetType;
+            if (implementsInterfaceOrSubclassOf(source, lub.getSuperClass())) 
return true;
+            for (ClassNode classNode : lub.getInterfaces()) {
+                if (source.implementsInterface(classNode)) return true;
+            }
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java 
b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
new file mode 100644
index 0000000..e514b59
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
@@ -0,0 +1,344 @@
+/*
+ *  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.classgen;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.control.ErrorCollector;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.vmplugin.VMPluginFactory;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An Annotation visitor responsible for:
+ * <ul>
+ * <li>reading annotation metadata (@Retention, @Target, attribute types)</li>
+ * <li>verify that an <code>AnnotationNode</code> conforms to annotation 
meta</li>
+ * <li>enhancing an <code>AnnotationNode</code> AST to reflect real annotation 
meta</li>
+ * </ul>
+ */
+public class AnnotationVisitor {
+    private final SourceUnit source;
+    private final ErrorCollector errorCollector;
+    private AnnotationNode annotation;
+    private ClassNode reportClass;
+
+    public AnnotationVisitor(SourceUnit source, ErrorCollector errorCollector) 
{
+        this.source = source;
+        this.errorCollector = errorCollector;
+    }
+
+    public void setReportClass(ClassNode cn) {
+        reportClass = cn;
+    }
+
+    public AnnotationNode visit(AnnotationNode node) {
+        this.annotation = node;
+        this.reportClass = node.getClassNode();
+
+        if (!isValidAnnotationClass(node.getClassNode())) {
+            addError("class " + node.getClassNode().getName() + " is not an 
annotation");
+            return node;
+        }
+
+        // check if values have been passed for all annotation attributes that 
don't have defaults
+        if (!checkIfMandatoryAnnotationValuesPassed(node)) {
+            return node;
+        }
+
+        // if enum constants have been used, check if they are all valid
+        if (!checkIfValidEnumConstsAreUsed(node)) {
+            return node;
+        }
+        
+        Map<String, Expression> attributes = node.getMembers();
+        for (Map.Entry<String, Expression> entry : attributes.entrySet()) {
+            String attrName = entry.getKey();
+            Expression attrExpr = transformInlineConstants(entry.getValue());
+            entry.setValue(attrExpr);
+            ClassNode attrType = getAttributeType(node, attrName);
+            visitExpression(attrName, attrExpr, attrType);
+        }
+        VMPluginFactory.getPlugin().configureAnnotation(node);
+        return this.annotation;
+    }
+    
+    private boolean checkIfValidEnumConstsAreUsed(AnnotationNode node) {
+        Map<String, Expression> attributes = node.getMembers();
+        for (Map.Entry<String, Expression> entry : attributes.entrySet()) {
+            if (!validateEnumConstant(entry.getValue()))
+                return false;
+        }
+        return true;
+    }
+    
+    private boolean validateEnumConstant(Expression exp) {
+        if (exp instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) exp;
+            String name = pe.getPropertyAsString();
+            if (pe.getObjectExpression() instanceof ClassExpression && name != 
null) {
+                ClassExpression ce = (ClassExpression) 
pe.getObjectExpression();
+                ClassNode type = ce.getType();
+                if (type.isEnum()) {
+                    boolean ok = false;
+                    try {
+                        FieldNode enumField = type.getDeclaredField(name);
+                        ok = enumField != null && 
enumField.getType().equals(type);
+                    } catch(Exception ex) {
+                        // ignore
+                    }
+                    if(!ok) {
+                        addError("No enum const " + type.getName() + "." + 
name, pe);
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    private Expression transformInlineConstants(Expression exp) {
+        if (exp instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) exp;
+            if (pe.getObjectExpression() instanceof ClassExpression) {
+                ClassExpression ce = (ClassExpression) 
pe.getObjectExpression();
+                ClassNode type = ce.getType();
+                if (type.isEnum() || !type.isResolved())
+                    return exp;
+
+                try {
+                    Field field = 
type.getTypeClass().getField(pe.getPropertyAsString());
+                    if (field != null && 
Modifier.isStatic(field.getModifiers()) && 
Modifier.isFinal(field.getModifiers())) {
+                        return new ConstantExpression(field.get(null));
+                    }
+                } catch(Exception e) {
+                    // ignore, leave property expression in place and we'll 
report later
+                }
+            }
+        } else if (exp instanceof ListExpression) {
+            ListExpression le = (ListExpression) exp;
+            ListExpression result = new ListExpression();
+            for (Expression e : le.getExpressions()) {
+                result.addExpression(transformInlineConstants(e));
+            }
+            return result;
+        }
+        return exp;
+    }
+
+    private boolean checkIfMandatoryAnnotationValuesPassed(AnnotationNode 
node) {
+        boolean ok = true;
+        Map attributes = node.getMembers();
+        ClassNode classNode = node.getClassNode();
+        for (MethodNode mn : classNode.getMethods()) {
+            String methodName = mn.getName();
+            // if the annotation attribute has a default, getCode() returns a 
ReturnStatement with the default value
+            if (mn.getCode() == null && !attributes.containsKey(methodName)) {
+                addError("No explicit/default value found for annotation 
attribute '" + methodName + "'", node);
+                ok = false;
+            }
+        }
+        return ok;
+    }
+
+    private ClassNode getAttributeType(AnnotationNode node, String attrName) {
+        ClassNode classNode = node.getClassNode();
+        List methods = classNode.getMethods(attrName);
+        // if size is >1, then the method was overwritten or something, we 
ignore that
+        // if it is an error, we have to test it at another place. But size==0 
is
+        // an error, because it means that no such attribute exists.
+        if (methods.isEmpty()) {
+            addError("'" + attrName + "'is not part of the annotation " + 
classNode, node);
+            return ClassHelper.OBJECT_TYPE;
+        }
+        MethodNode method = (MethodNode) methods.get(0);
+        return method.getReturnType();
+    }
+
+    private static boolean isValidAnnotationClass(ClassNode node) {
+        return node.implementsInterface(ClassHelper.Annotation_TYPE);
+    }
+
+    protected void visitExpression(String attrName, Expression attrExp, 
ClassNode attrType) {
+        if (attrType.isArray()) {
+            // check needed as @Test(attr = {"elem"}) passes through the parser
+            if (attrExp instanceof ListExpression) {
+                ListExpression le = (ListExpression) attrExp;
+                visitListExpression(attrName, le, attrType.getComponentType());
+            } else if (attrExp instanceof ClosureExpression) {
+                addError("Annotation list attributes must use Groovy notation 
[el1, el2]", attrExp);
+            } else {
+                // treat like a singleton list as per Java
+                ListExpression listExp = new ListExpression();
+                listExp.addExpression(attrExp);
+                if (annotation != null) {
+                    annotation.setMember(attrName, listExp);
+                }
+                visitExpression(attrName, listExp, attrType);
+            }
+        } else if (ClassHelper.isPrimitiveType(attrType)) {
+            visitConstantExpression(attrName, getConstantExpression(attrExp, 
attrType), ClassHelper.getWrapper(attrType));
+        } else if (ClassHelper.STRING_TYPE.equals(attrType)) {
+            visitConstantExpression(attrName, getConstantExpression(attrExp, 
attrType), ClassHelper.STRING_TYPE);
+        } else if (ClassHelper.CLASS_Type.equals(attrType)) {
+            if (!(attrExp instanceof ClassExpression || attrExp instanceof 
ClosureExpression)) {
+                addError("Only classes and closures can be used for attribute 
'" + attrName + "'", attrExp);
+            }
+        } else if (attrType.isDerivedFrom(ClassHelper.Enum_Type)) {
+            if (attrExp instanceof PropertyExpression) {
+                visitEnumExpression(attrName, (PropertyExpression) attrExp, 
attrType);
+            } else {
+                addError("Expected enum value for attribute " + attrName, 
attrExp);
+            }
+        } else if (isValidAnnotationClass(attrType)) {
+            if (attrExp instanceof AnnotationConstantExpression) {
+                visitAnnotationExpression(attrName, 
(AnnotationConstantExpression) attrExp, attrType);
+            } else {
+                addError("Expected annotation of type '" + attrType.getName() 
+ "' for attribute " + attrName, attrExp);
+            }
+        } else {
+            addError("Unexpected type " + attrType.getName(), attrExp);
+        }
+    }
+
+    public void checkReturnType(ClassNode attrType, ASTNode node) {
+        if (attrType.isArray()) {
+            checkReturnType(attrType.getComponentType(), node);
+        } else if (ClassHelper.isPrimitiveType(attrType)) {
+            return;
+        } else if (ClassHelper.STRING_TYPE.equals(attrType)) {
+            return;
+        } else if (ClassHelper.CLASS_Type.equals(attrType)) {
+            return;
+        } else if (attrType.isDerivedFrom(ClassHelper.Enum_Type)) {
+            return;
+        } else if (isValidAnnotationClass(attrType)) {
+            return;
+        } else {
+            addError("Unexpected return type " + attrType.getName(), node);
+        }
+    }
+
+    private ConstantExpression getConstantExpression(Expression exp, ClassNode 
attrType) {
+        if (exp instanceof ConstantExpression) {
+            return (ConstantExpression) exp;
+        }
+        String base = "Expected '" + exp.getText() + "' to be an inline 
constant of type " + attrType.getName();
+        if (exp instanceof PropertyExpression) {
+            addError(base + " not a property expression", exp);
+        } else if (exp instanceof VariableExpression && 
((VariableExpression)exp).getAccessedVariable() instanceof FieldNode) {
+            addError(base + " not a field expression", exp);
+        } else {
+            addError(base, exp);
+        }
+        return ConstantExpression.EMPTY_EXPRESSION;
+    }
+
+    /**
+     * @param attrName   the name
+     * @param expression the expression
+     * @param attrType   the type
+     */
+    protected void visitAnnotationExpression(String attrName, 
AnnotationConstantExpression expression, ClassNode attrType) {
+        AnnotationNode annotationNode = (AnnotationNode) expression.getValue();
+        AnnotationVisitor visitor = new AnnotationVisitor(this.source, 
this.errorCollector);
+        // TODO track Deprecated usage and give a warning?
+        visitor.visit(annotationNode);
+    }
+
+    protected void visitListExpression(String attrName, ListExpression 
listExpr, ClassNode elementType) {
+        for (Expression expression : listExpr.getExpressions()) {
+            visitExpression(attrName, expression, elementType);
+        }
+    }
+
+    protected void visitConstantExpression(String attrName, ConstantExpression 
constExpr, ClassNode attrType) {
+        ClassNode constType = constExpr.getType();
+        ClassNode wrapperType = ClassHelper.getWrapper(constType);
+        if (!hasCompatibleType(attrType, wrapperType)) {
+            addError("Attribute '" + attrName + "' should have type '" + 
attrType.getName()
+                    + "'; but found type '" + constType.getName() + "'", 
constExpr);
+        }
+    }
+
+    private static boolean hasCompatibleType(ClassNode attrType, ClassNode 
wrapperType) {
+        return wrapperType.isDerivedFrom(ClassHelper.getWrapper(attrType));
+    }
+
+    protected void visitEnumExpression(String attrName, PropertyExpression 
propExpr, ClassNode attrType) {
+        if (!propExpr.getObjectExpression().getType().isDerivedFrom(attrType)) 
{
+            addError("Attribute '" + attrName + "' should have type '" + 
attrType.getName() + "' (Enum), but found "
+                    + propExpr.getObjectExpression().getType().getName(),
+                    propExpr);
+        }
+    }
+
+    protected void addError(String msg) {
+        addError(msg, this.annotation);
+    }
+
+    protected void addError(String msg, ASTNode expr) {
+        this.errorCollector.addErrorAndContinue(
+                new SyntaxErrorMessage(new SyntaxException(msg + " in @" + 
this.reportClass.getName() + '\n', expr.getLineNumber(), 
expr.getColumnNumber(), expr.getLastLineNumber(), expr.getLastColumnNumber()), 
this.source)
+        );
+    }
+
+    public void checkCircularReference(ClassNode searchClass, ClassNode 
attrType, Expression startExp) {
+        if (!isValidAnnotationClass(attrType)) return;
+        if (!(startExp instanceof AnnotationConstantExpression)) {
+            addError("Found '" + startExp.getText() + "' when expecting an 
Annotation Constant", startExp);
+            return;
+        }
+        AnnotationConstantExpression ace = (AnnotationConstantExpression) 
startExp;
+        AnnotationNode annotationNode = (AnnotationNode) ace.getValue();
+        if (annotationNode.getClassNode().equals(searchClass)) {
+            addError("Circular reference discovered in " + 
searchClass.getName(), startExp);
+            return;
+        }
+        ClassNode cn = annotationNode.getClassNode();
+        for (MethodNode method : cn.getMethods()) {
+            if (method.getReturnType().equals(searchClass)) {
+                addError("Circular reference discovered in " + cn.getName(), 
startExp);
+            }
+            ReturnStatement code = (ReturnStatement) method.getCode();
+            if (code == null) continue;
+            checkCircularReference(searchClass, method.getReturnType(), 
code.getExpression());
+        }
+    }
+
+}

Reply via email to