blob: 374239cd416903e92386cdb8da1e90c35dce7dec [file] [log] [blame]
/*
* 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;
import static org.codehaus.groovy.ast.GenericsType.GenericsTypeName;
/**
* 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.
*/
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>() {
private static final long serialVersionUID = -5178744121420941913L;
{
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<GenericsTypeName, 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];
GenericsTypeName gtn = new GenericsTypeName(genericsType.getName());
if (genericsType.isPlaceholder() && genericsTypeMap.containsKey(gtn)) {
copyTypes[i] = genericsTypeMap.get(gtn);
} 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 being 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(ClassNode.EMPTY_ARRAY);
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 and 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(GenericsType.EMPTY_ARRAY));
}
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;
}
}