From 86d7d24e9ce2a31a0bdae40c65845fbe345060b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bendeg=C3=BAz=20Nagy?= Date: Fri, 18 Aug 2017 21:05:54 +0200 Subject: [PATCH] Java, typeres: model intersection types --- .../typedefinition/JavaTypeDefinition.java | 437 ++++-------------- .../JavaTypeDefinitionIntersection.java | 190 ++++++++ .../JavaTypeDefinitionSimple.java | 381 +++++++++++++++ .../typeinference/InferenceRuleType.java | 2 +- 4 files changed, 665 insertions(+), 345 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionIntersection.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java index 903a86c25a..452d9820b8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java @@ -17,149 +17,105 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class JavaTypeDefinition implements TypeDefinition { +public abstract class JavaTypeDefinition implements TypeDefinition { // contains non-generic and raw EXACT types private static final Map, JavaTypeDefinition> CLASS_EXACT_TYPE_DEF_CACHE = new HashMap<>(); - // contains non-generic and raw UPPER_BOUND types, does not contain compound types + // contains non-generic and raw UPPER_BOUND types, does not contain intersection types private static final Map, JavaTypeDefinition> CLASS_UPPER_BOUND_TYPE_DEF_CACHE = new HashMap<>(); - // contains non-generic and raw LOWER_BOUND types, does not contain compound types + // contains non-generic and raw LOWER_BOUND types, does not contain intersection types private static final Map, JavaTypeDefinition> CLASS_LOWER_BOUND_TYPE_DEF_CACHE = new HashMap<>(); - private final Class clazz; - private final List genericArgs; - // cached because calling clazz.getTypeParameters().length create a new array every time - private final int typeParameterCount; - private final boolean isGeneric; - private final JavaTypeDefinition enclosingClass; private final TypeDefinitionType definitionType; - // only used when definitionType == LOWER_BOUND - private final JavaTypeDefinition lowerBound; - - public enum TypeDefinitionType { - EXACT, UPPER_BOUND, LOWER_BOUND + EXACT, UPPER_BOUND, LOWER_BOUND, INTERSECTION } - private JavaTypeDefinition(final Class clazz, TypeDefinitionType definitionType, - JavaTypeDefinition lowerBound) { - this.clazz = clazz; - this.typeParameterCount = clazz.getTypeParameters().length; + protected JavaTypeDefinition(TypeDefinitionType definitionType) { this.definitionType = definitionType; - this.lowerBound = lowerBound; - - final TypeVariable[] typeParameters; - // the anonymous class can't have generics, but we may be binding generics from super classes - if (clazz.isAnonymousClass()) { - // is this an anonymous class based on an interface or a class? - if (clazz.getInterfaces().length != 0) { - typeParameters = clazz.getInterfaces()[0].getTypeParameters(); - } else { - typeParameters = clazz.getSuperclass().getTypeParameters(); - } - } else { - typeParameters = clazz.getTypeParameters(); - } - - isGeneric = typeParameters.length != 0; - if (isGeneric) { - // Generics will be lazily loaded - this.genericArgs = new ArrayList(typeParameters.length); - } else { - this.genericArgs = Collections.emptyList(); - } - - enclosingClass = forClass(clazz.getEnclosingClass()); } - public static JavaTypeDefinition forClass(final Class clazz) { - return forClass(clazz, TypeDefinitionType.EXACT); + public static JavaTypeDefinition forClassIntersetion(List intersectionTypes) { + return new JavaTypeDefinitionIntersection(intersectionTypes); } - public static JavaTypeDefinition forClass(final Class clazz, TypeDefinitionType definitionType) { + public static JavaTypeDefinition forClassLower(JavaTypeDefinition lowerBound) { + if(lowerBound == null) { + return null; + } + + if (!lowerBound.isGeneric() || lowerBound.isRawType()) { + return new JavaTypeDefinitionSimple(lowerBound); + } + + final JavaTypeDefinition typeDef = CLASS_LOWER_BOUND_TYPE_DEF_CACHE.get(lowerBound.getType()); + + if (typeDef != null) { + return typeDef; + } + + final JavaTypeDefinition newDef = new JavaTypeDefinitionSimple(lowerBound); + + CLASS_LOWER_BOUND_TYPE_DEF_CACHE.put(lowerBound.getType(), newDef); + + return newDef; + } + + public static JavaTypeDefinition forClassUpper(Class clazz, JavaTypeDefinition... boundGenerics) { if (clazz == null) { return null; } - switch (definitionType) { - case EXACT: { - final JavaTypeDefinition typeDef = CLASS_EXACT_TYPE_DEF_CACHE.get(clazz); - - if (typeDef != null) { - return typeDef; - } - - final JavaTypeDefinition newDef = new JavaTypeDefinition(clazz, definitionType, null); - - CLASS_EXACT_TYPE_DEF_CACHE.put(clazz, newDef); - - return newDef; - } - case UPPER_BOUND: { - final JavaTypeDefinition typeDef = CLASS_UPPER_BOUND_TYPE_DEF_CACHE.get(clazz); - - if (typeDef != null) { - return typeDef; - } - - final JavaTypeDefinition newDef = new JavaTypeDefinition(clazz, definitionType, null); - - CLASS_UPPER_BOUND_TYPE_DEF_CACHE.put(clazz, newDef); - - return newDef; - } - case LOWER_BOUND: { - final JavaTypeDefinition typeDef = CLASS_LOWER_BOUND_TYPE_DEF_CACHE.get(clazz); - - if (typeDef != null) { - return typeDef; - } - - final JavaTypeDefinition newDef = new JavaTypeDefinition(Object.class, definitionType, - JavaTypeDefinition.forClass(clazz)); - - CLASS_LOWER_BOUND_TYPE_DEF_CACHE.put(clazz, newDef); - - return newDef; - } - default: - throw new IllegalStateException("Unknown definition type"); + if (boundGenerics.length != 0) { + // no caching with generics + return new JavaTypeDefinitionSimple(TypeDefinitionType.UPPER_BOUND, clazz, boundGenerics); } + + final JavaTypeDefinition typeDef = CLASS_UPPER_BOUND_TYPE_DEF_CACHE.get(clazz); + + if (typeDef != null) { + return typeDef; + } + + final JavaTypeDefinition newDef = new JavaTypeDefinitionSimple(TypeDefinitionType.UPPER_BOUND, clazz); + + CLASS_UPPER_BOUND_TYPE_DEF_CACHE.put(clazz, newDef); + + return newDef; } - public static JavaTypeDefinition forClass(final Class clazz, final JavaTypeDefinition... boundGenerics) { - return forClass(clazz, TypeDefinitionType.EXACT, boundGenerics); - } - - public static JavaTypeDefinition forClass(final Class clazz, final TypeDefinitionType definitionType, - final JavaTypeDefinition... boundGenerics) { + public static JavaTypeDefinition forClass(final Class clazz, JavaTypeDefinition... boundGenerics) { if (clazz == null) { return null; } - if (definitionType == TypeDefinitionType.LOWER_BOUND) { + // deal with generic types + if (boundGenerics.length != 0) { // With generics there is no cache - return new JavaTypeDefinition(Object.class, definitionType, forClass(clazz, boundGenerics)); + return new JavaTypeDefinitionSimple(TypeDefinitionType.EXACT, clazz, boundGenerics); } - // With generics there is no cache - final JavaTypeDefinition typeDef = new JavaTypeDefinition(clazz, definitionType, null); - ; - Collections.addAll(typeDef.genericArgs, boundGenerics); + final JavaTypeDefinition typeDef = CLASS_EXACT_TYPE_DEF_CACHE.get(clazz); - return typeDef; + if (typeDef != null) { + return typeDef; + } + + final JavaTypeDefinition newDef = new JavaTypeDefinitionSimple(TypeDefinitionType.EXACT, clazz); + + CLASS_EXACT_TYPE_DEF_CACHE.put(clazz, newDef); + + return newDef; } @Override - public Class getType() { - return clazz; - } + public abstract Class getType(); - public boolean isGeneric() { - return !genericArgs.isEmpty(); - } + public abstract JavaTypeDefinition getEnclosingClass(); + + public abstract boolean isGeneric(); public static int getGenericTypeIndex(TypeVariable[] typeParameters, final String parameterName) { for (int i = 0; i < typeParameters.length; i++) { @@ -171,242 +127,43 @@ public class JavaTypeDefinition implements TypeDefinition { return -1; } - private JavaTypeDefinition getGenericType(final String parameterName, Method method, - List methodTypeArgumens) { - if (method != null && methodTypeArgumens != null) { - int paramIndex = getGenericTypeIndex(method.getTypeParameters(), parameterName); - if (paramIndex != -1) { - return methodTypeArgumens.get(paramIndex); - } - } + public abstract JavaTypeDefinition getGenericType(final String parameterName); - return getGenericType(parameterName); - } + public abstract JavaTypeDefinition getGenericType(final int index); - public JavaTypeDefinition getGenericType(final String parameterName) { - for (JavaTypeDefinition currTypeDef = this; currTypeDef != null; currTypeDef = currTypeDef.enclosingClass) { - int paramIndex = getGenericTypeIndex(currTypeDef.clazz.getTypeParameters(), parameterName); - if (paramIndex != -1) { - return currTypeDef.getGenericType(paramIndex); - } - } + public abstract JavaTypeDefinition resolveTypeDefinition(final Type type); - // throw because we could not find parameterName - StringBuilder builder = new StringBuilder("No generic parameter by name ").append(parameterName); - for (JavaTypeDefinition currTypeDef = this; currTypeDef != null; currTypeDef = currTypeDef.enclosingClass) { - builder.append("\n on class "); - builder.append(clazz.getSimpleName()); - } + public abstract JavaTypeDefinition resolveTypeDefinition(final Type type, Method method, + List methodTypeArgs); - throw new IllegalArgumentException(builder.toString()); - } + public abstract JavaTypeDefinition getComponentType(); - public JavaTypeDefinition getGenericType(final int index) { - // Check if it has been lazily initialized first - if (genericArgs.size() > index) { - final JavaTypeDefinition cachedDefinition = genericArgs.get(index); - if (cachedDefinition != null) { - return cachedDefinition; - } - } + public abstract boolean isClassOrInterface(); - // Force the list to have enough elements - for (int i = genericArgs.size(); i <= index; i++) { - genericArgs.add(null); - } - - /* - * Set a default to circuit-brake any recursions (ie: raw types with no generic info) - * Object.class is a right answer in those scenarios - */ - genericArgs.set(index, forClass(Object.class)); + public abstract boolean isNullType(); - final TypeVariable typeVariable = clazz.getTypeParameters()[index]; - final JavaTypeDefinition typeDefinition = resolveTypeDefinition(typeVariable.getBounds()[0]); + public abstract boolean isPrimitive(); - // cache result - genericArgs.set(index, typeDefinition); - return typeDefinition; - } + public abstract boolean hasSameErasureAs(JavaTypeDefinition def); - public JavaTypeDefinition resolveTypeDefinition(final Type type) { - return resolveTypeDefinition(type, null, null); - } + public abstract int getTypeParameterCount(); - public JavaTypeDefinition resolveTypeDefinition(final Type type, Method method, - List methodTypeArgs) { - if (type == null) { - // Without more info, this is all we can tell... - return forClass(Object.class); - } - - if (type instanceof Class) { // Raw types take this branch as well - return forClass((Class) type); - } else if (type instanceof ParameterizedType) { - final ParameterizedType parameterizedType = (ParameterizedType) type; - - // recursively determine each type argument's type def. - final Type[] typeArguments = parameterizedType.getActualTypeArguments(); - final JavaTypeDefinition[] genericBounds = new JavaTypeDefinition[typeArguments.length]; - for (int i = 0; i < typeArguments.length; i++) { - genericBounds[i] = resolveTypeDefinition(typeArguments[i], method, methodTypeArgs); - } - - // TODO : is this cast safe? - return forClass((Class) parameterizedType.getRawType(), genericBounds); - } else if (type instanceof TypeVariable) { - return getGenericType(((TypeVariable) type).getName(), method, methodTypeArgs); - } else if (type instanceof WildcardType) { - final Type[] wildcardUpperBounds = ((WildcardType) type).getUpperBounds(); - if (wildcardUpperBounds.length != 0) { // upper bound wildcard - return resolveTypeDefinition(wildcardUpperBounds[0], method, methodTypeArgs); - } else { // lower bound wildcard - return forClass(Object.class); - } - } - - // TODO : Shall we throw here? - return forClass(Object.class); - } - - // TODO: are generics okay like this? - public JavaTypeDefinition getComponentType() { - Class componentType = getType().getComponentType(); - - if (componentType == null) { - throw new IllegalStateException(getType().getSimpleName() + " is not an array type!"); - } - - return forClass(componentType); - } - - public boolean isClassOrInterface() { - return !clazz.isEnum() && !clazz.isPrimitive() && !clazz.isAnnotation() && !clazz.isArray(); - } - - public boolean isNullType() { - return false; - } - - public boolean isPrimitive() { - return clazz.isPrimitive(); - } - - public boolean equivalent(JavaTypeDefinition def) { - // TODO: JavaTypeDefinition generic equality - return clazz.equals(def.clazz) && getTypeParameterCount() == def.getTypeParameterCount(); - } - - public boolean hasSameErasureAs(JavaTypeDefinition def) { - return clazz == def.clazz; - } - - public int getTypeParameterCount() { - return typeParameterCount; - } - - public boolean isArrayType() { - return clazz.isArray(); - } + public abstract boolean isArrayType(); @Override - public String toString() { - return new StringBuilder("JavaTypeDefinition [clazz=").append(clazz) - .append(", definitionType=").append(definitionType) - .append(", genericArgs=").append(genericArgs) - .append(", isGeneric=").append(isGeneric) - .append(", lowerBound=").append(lowerBound) - .append(']').toString(); - - } + public abstract String toString(); @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof JavaTypeDefinition)) { - return false; - } - - // raw vs raw - // we assume that this covers raw types, because they are cached - if (this == obj) { - return true; - } - - JavaTypeDefinition otherTypeDef = (JavaTypeDefinition) obj; - - if (clazz != otherTypeDef.clazz - || definitionType != otherTypeDef.definitionType) { - return false; - } - - // This should cover - // raw vs proper - // proper vs raw - // proper vs proper - - // Note: we have to force raw types to compute their generic args, class Stuff> - // Stuff a; - // Stuff> b; - // Stuff> c; - // all of the above should be equal - - - if(isLowerBound() && !lowerBound.equals(otherTypeDef.lowerBound)) { - return false; - } else { - for (int i = 0; i < getTypeParameterCount(); ++i) { - // Note: we assume that cycles can only exist because of raw types - if (!getGenericType(i).equals(otherTypeDef.getGenericType(i))) { - return false; - } - } - } - - return true; - } + public abstract boolean equals(Object obj); @Override - public int hashCode() { - return clazz.hashCode(); - } + public abstract int hashCode(); - public Set getSuperTypeSet() { - return getSuperTypeSet(new HashSet()); - } + public abstract Set getSuperTypeSet(); - private Set getSuperTypeSet(Set destinationSet) { - destinationSet.add(this); + protected abstract Set getSuperTypeSet(Set destinationSet); - if (this.clazz != Object.class) { - - resolveTypeDefinition(clazz.getGenericSuperclass()).getSuperTypeSet(destinationSet); - - for (Type type : clazz.getGenericInterfaces()) { - resolveTypeDefinition(type).getSuperTypeSet(destinationSet); - } - } - - return destinationSet; - } - - public Set> getErasedSuperTypeSet() { - Set> result = new HashSet<>(); - result.add(Object.class); - return getErasedSuperTypeSet(this.clazz, result); - } - - private static Set> getErasedSuperTypeSet(Class clazz, Set> destinationSet) { - if (clazz != null) { - destinationSet.add(clazz); - getErasedSuperTypeSet(clazz.getSuperclass(), destinationSet); - - for (Class interfaceType : clazz.getInterfaces()) { - getErasedSuperTypeSet(interfaceType, destinationSet); - } - } - - return destinationSet; - } + public abstract Set> getErasedSuperTypeSet(); /** * @return true if clazz is generic and had not been parameterized @@ -415,41 +172,33 @@ public class JavaTypeDefinition implements TypeDefinition { return isGeneric() && CLASS_EXACT_TYPE_DEF_CACHE.containsKey(getType()); } - public JavaTypeDefinition getAsSuper(Class superClazz) { - if (clazz == superClazz) { // optimize for same class calls - return this; - } - - for (JavaTypeDefinition superTypeDef : getSuperTypeSet()) { - if (superTypeDef.clazz == superClazz) { - return superTypeDef; - } - } - - return null; - } + public abstract JavaTypeDefinition getAsSuper(Class superClazz); public boolean isExactType() { return definitionType == TypeDefinitionType.EXACT; } public boolean isUpperBound() { - return definitionType == TypeDefinitionType.UPPER_BOUND; + return definitionType == TypeDefinitionType.UPPER_BOUND + // intersection types can only be upper bounds in java + || definitionType == TypeDefinitionType.INTERSECTION; } public boolean isLowerBound() { return definitionType == TypeDefinitionType.LOWER_BOUND; } + public boolean isIntersectionType() { + return definitionType == TypeDefinitionType.INTERSECTION; + } + public TypeDefinitionType getDefinitionType() { return definitionType; } - public JavaTypeDefinition getLowerBound() { - if (definitionType != TypeDefinitionType.LOWER_BOUND) { - throw new IllegalStateException("Not a lower bound type: " + toString()); - } + public abstract JavaTypeDefinition getIntersectionType(int index); - return lowerBound; - } + public abstract int getIntersectionTypeCount(); + + public abstract JavaTypeDefinition getLowerBound(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionIntersection.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionIntersection.java new file mode 100644 index 0000000000..0c23d9abf9 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionIntersection.java @@ -0,0 +1,190 @@ +package net.sourceforge.pmd.lang.java.typeresolution.typedefinition; + +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition.TypeDefinitionType + .INTERSECTION; + +public class JavaTypeDefinitionIntersection extends JavaTypeDefinition { + private List intersectionTypes; + + protected JavaTypeDefinitionIntersection(List intersectionTypes) { + super(INTERSECTION); + + if (intersectionTypes.isEmpty()) { + throw new IllegalArgumentException("Intersection type list can't be empty"); + } + + this.intersectionTypes = Collections.unmodifiableList(new ArrayList<>(intersectionTypes)); + } + + @Override + public Class getType() { + return intersectionTypes.get(0).getType(); + } + + @Override + public JavaTypeDefinition getEnclosingClass() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isGeneric() { + throw new UnsupportedOperationException(); + } + + @Override + public JavaTypeDefinition getGenericType(String parameterName) { + throw new UnsupportedOperationException(); + } + + @Override + public JavaTypeDefinition getGenericType(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public JavaTypeDefinition resolveTypeDefinition(Type type) { + throw new UnsupportedOperationException(); + } + + @Override + public JavaTypeDefinition resolveTypeDefinition(Type type, Method method, List methodTypeArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public JavaTypeDefinition getComponentType() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isClassOrInterface() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNullType() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPrimitive() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasSameErasureAs(JavaTypeDefinition def) { + throw new UnsupportedOperationException(); + } + + @Override + public int getTypeParameterCount() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isArrayType() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Intersection type ["); + builder.append(intersectionTypes.get(0)); + for (int index = 1; index < intersectionTypes.size(); ++index) { + builder.append(" && "); + builder.append(intersectionTypes.get(index)); + } + return builder.append("]").toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof JavaTypeDefinitionIntersection)) { + return false; + } + + if (this == obj) { + return true; + } + + JavaTypeDefinitionIntersection otherTypeDef = (JavaTypeDefinitionIntersection) obj; + + if(otherTypeDef.getIntersectionTypeCount() != getIntersectionTypeCount()) { + return false; + } + + + + // we assume that the intersectionTypes list cannot contain duplicates, then indeed, this will prove equality + outer: + for (JavaTypeDefinition intersectionTypeDef : intersectionTypes) { + for (JavaTypeDefinition otherIntersectionTypeDef : otherTypeDef.intersectionTypes) { + if (intersectionTypeDef.equals(otherIntersectionTypeDef)) { + continue outer; + } + } + + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = 31; + + for (JavaTypeDefinition typeDef : intersectionTypes) { + result *= typeDef.hashCode(); + } + + return result; + } + + @Override + public Set getSuperTypeSet() { + throw new UnsupportedOperationException(""); + } + + @Override + protected Set getSuperTypeSet(Set destinationSet) { + throw new UnsupportedOperationException(""); + } + + @Override + public Set> getErasedSuperTypeSet() { + throw new UnsupportedOperationException(""); + } + + @Override + public boolean isRawType() { + throw new UnsupportedOperationException(""); + } + + @Override + public JavaTypeDefinition getAsSuper(Class superClazz) { + throw new UnsupportedOperationException(""); + } + + @Override + public JavaTypeDefinition getIntersectionType(int index) { + return intersectionTypes.get(index); + } + + @Override + public int getIntersectionTypeCount() { + return intersectionTypes.size(); + } + + @Override + public JavaTypeDefinition getLowerBound() { + throw new UnsupportedOperationException("Not a lower bound"); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java new file mode 100644 index 0000000000..1e8bdb1087 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java @@ -0,0 +1,381 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.typeresolution.typedefinition; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/* default */ class JavaTypeDefinitionSimple extends JavaTypeDefinition { + private final Class clazz; + private final List genericArgs; + // cached because calling clazz.getTypeParameters().length create a new array every time + private final int typeParameterCount; + private final boolean isGeneric; + private final JavaTypeDefinition enclosingClass; + + // only used when definitionType == LOWER_BOUND + private final JavaTypeDefinition lowerBound; + + protected JavaTypeDefinitionSimple(JavaTypeDefinition lowerBound) { + this(TypeDefinitionType.LOWER_BOUND, lowerBound, Object.class); + } + + protected JavaTypeDefinitionSimple(TypeDefinitionType definitionType, Class clazz, + JavaTypeDefinition... boundGenerics) { + this(definitionType, null, clazz, boundGenerics); + } + + protected JavaTypeDefinitionSimple(TypeDefinitionType definitionType, JavaTypeDefinition lowerBound, + final Class clazz, JavaTypeDefinition... boundGenerics) { + super(definitionType); + + if (definitionType == TypeDefinitionType.LOWER_BOUND && lowerBound == null) { + throw new IllegalStateException("Constructing a lower bound type with invalid lower bound argument"); + } else if (definitionType != TypeDefinitionType.LOWER_BOUND && lowerBound != null) { + throw new IllegalStateException("Not a lower bound type while providing a valid lower bound"); + } + + this.clazz = clazz; + this.lowerBound = lowerBound; + + final TypeVariable[] typeParameters; + // the anonymous class can't have generics, but we may be binding generics from super classes + if (clazz.isAnonymousClass()) { + // is this an anonymous class based on an interface or a class? + if (clazz.getInterfaces().length != 0) { + typeParameters = clazz.getInterfaces()[0].getTypeParameters(); + } else { + typeParameters = clazz.getSuperclass().getTypeParameters(); + } + } else { + typeParameters = clazz.getTypeParameters(); + } + + typeParameterCount = typeParameters.length; + isGeneric = typeParameters.length != 0; + + if (isGeneric) { + // Generics will be lazily loaded + this.genericArgs = new ArrayList<>(typeParameters.length); + // boundGenerics would be empty if this is a raw type, hence the lazy loading + Collections.addAll(this.genericArgs, boundGenerics); + } else { + this.genericArgs = Collections.emptyList(); + } + + enclosingClass = forClass(clazz.getEnclosingClass()); + } + + @Override + public Class getType() { + return clazz; + } + + @Override + public JavaTypeDefinition getEnclosingClass() { + return enclosingClass; + } + + @Override + public boolean isGeneric() { + return !genericArgs.isEmpty(); + } + + private JavaTypeDefinition getGenericType(final String parameterName, Method method, + List methodTypeArgumens) { + if (method != null && methodTypeArgumens != null) { + int paramIndex = getGenericTypeIndex(method.getTypeParameters(), parameterName); + if (paramIndex != -1) { + return methodTypeArgumens.get(paramIndex); + } + } + + return getGenericType(parameterName); + } + + @Override + public JavaTypeDefinition getGenericType(final String parameterName) { + for (JavaTypeDefinition currTypeDef = this; currTypeDef != null; currTypeDef = currTypeDef.getEnclosingClass()) { + int paramIndex = getGenericTypeIndex(currTypeDef.getType().getTypeParameters(), parameterName); + if (paramIndex != -1) { + return currTypeDef.getGenericType(paramIndex); + } + } + + // throw because we could not find parameterName + StringBuilder builder = new StringBuilder("No generic parameter by name ").append(parameterName); + for (JavaTypeDefinition currTypeDef = this; currTypeDef != null; currTypeDef = currTypeDef.getEnclosingClass()) { + builder.append("\n on class "); + builder.append(clazz.getSimpleName()); + } + + throw new IllegalArgumentException(builder.toString()); + } + + @Override + public JavaTypeDefinition getGenericType(final int index) { + // Check if it has been lazily initialized first + if (genericArgs.size() > index) { + final JavaTypeDefinition cachedDefinition = genericArgs.get(index); + if (cachedDefinition != null) { + return cachedDefinition; + } + } + + // Force the list to have enough elements + for (int i = genericArgs.size(); i <= index; i++) { + genericArgs.add(null); + } + + /* + * Set a default to circuit-brake any recursions (ie: raw types with no generic info) + * Object.class is a right answer in those scenarios + */ + genericArgs.set(index, forClass(Object.class)); + + final TypeVariable typeVariable = clazz.getTypeParameters()[index]; + final JavaTypeDefinition typeDefinition = resolveTypeDefinition(typeVariable.getBounds()[0]); + + // cache result + genericArgs.set(index, typeDefinition); + return typeDefinition; + } + + @Override + public JavaTypeDefinition resolveTypeDefinition(final Type type) { + return resolveTypeDefinition(type, null, null); + } + + @Override + public JavaTypeDefinition resolveTypeDefinition(final Type type, Method method, + List methodTypeArgs) { + if (type == null) { + // Without more info, this is all we can tell... + return forClass(Object.class); + } + + if (type instanceof Class) { // Raw types take this branch as well + return forClass((Class) type); + } else if (type instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + + // recursively determine each type argument's type def. + final Type[] typeArguments = parameterizedType.getActualTypeArguments(); + final JavaTypeDefinition[] genericBounds = new JavaTypeDefinition[typeArguments.length]; + for (int i = 0; i < typeArguments.length; i++) { + genericBounds[i] = resolveTypeDefinition(typeArguments[i], method, methodTypeArgs); + } + + // TODO : is this cast safe? + return forClass((Class) parameterizedType.getRawType(), genericBounds); + } else if (type instanceof TypeVariable) { + return getGenericType(((TypeVariable) type).getName(), method, methodTypeArgs); + } else if (type instanceof WildcardType) { + final Type[] wildcardUpperBounds = ((WildcardType) type).getUpperBounds(); + if (wildcardUpperBounds.length != 0) { // upper bound wildcard + return resolveTypeDefinition(wildcardUpperBounds[0], method, methodTypeArgs); + } else { // lower bound wildcard + return forClass(Object.class); + } + } + + // TODO : Shall we throw here? + return forClass(Object.class); + } + + // TODO: are generics okay like this? + public JavaTypeDefinition getComponentType() { + Class componentType = getType().getComponentType(); + + if (componentType == null) { + throw new IllegalStateException(getType().getSimpleName() + " is not an array type!"); + } + + return forClass(componentType); + } + + public boolean isClassOrInterface() { + return !clazz.isEnum() && !clazz.isPrimitive() && !clazz.isAnnotation() && !clazz.isArray(); + } + + public boolean isNullType() { + return false; + } + + public boolean isPrimitive() { + return clazz.isPrimitive(); + } + + public boolean equivalent(JavaTypeDefinition def) { + // TODO: JavaTypeDefinition generic equality + return clazz.equals(def.getType()) && getTypeParameterCount() == def.getTypeParameterCount(); + } + + public boolean hasSameErasureAs(JavaTypeDefinition def) { + return clazz == def.getType(); + } + + public int getTypeParameterCount() { + return typeParameterCount; + } + + public boolean isArrayType() { + return clazz.isArray(); + } + + @Override + public String toString() { + return new StringBuilder("JavaTypeDefinition [clazz=").append(clazz) + .append(", definitionType=").append(getDefinitionType()) + .append(", genericArgs=").append(genericArgs) + .append(", isGeneric=").append(isGeneric) + .append(", lowerBound=").append(lowerBound) + .append("]\n").toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof JavaTypeDefinitionSimple)) { + return false; + } + + // raw vs raw + // we assume that this covers raw types, because they are cached + if (this == obj) { + return true; + } + + JavaTypeDefinitionSimple otherTypeDef = (JavaTypeDefinitionSimple) obj; + + if (getDefinitionType() != otherTypeDef.getDefinitionType()) { + return false; + } + + if (isLowerBound() && !lowerBound.equals(otherTypeDef.lowerBound)) { + return false; + } else { + // This should cover + // raw vs proper + // proper vs raw + // proper vs proper + + if (clazz != otherTypeDef.clazz) { + return false; + } + + for (int i = 0; i < getTypeParameterCount(); ++i) { + // Note: we assume that cycles can only exist because of raw types + if (!getGenericType(i).equals(otherTypeDef.getGenericType(i))) { + return false; + } + } + } + + return true; + } + + @Override + public int hashCode() { + return clazz.hashCode(); + } + + @Override + public Set getSuperTypeSet() { + return getSuperTypeSet(new HashSet()); + } + + @Override + protected Set getSuperTypeSet(Set destinationSet) { + destinationSet.add(this); + + if (this.clazz != Object.class) { + + resolveTypeDefinition(clazz.getGenericSuperclass()).getSuperTypeSet(destinationSet); + + for (Type type : clazz.getGenericInterfaces()) { + resolveTypeDefinition(type).getSuperTypeSet(destinationSet); + } + } + + return destinationSet; + } + + public Set> getErasedSuperTypeSet() { + Set> result = new HashSet<>(); + result.add(Object.class); + return getErasedSuperTypeSet(this.clazz, result); + } + + private static Set> getErasedSuperTypeSet(Class clazz, Set> destinationSet) { + if (clazz != null) { + destinationSet.add(clazz); + getErasedSuperTypeSet(clazz.getSuperclass(), destinationSet); + + for (Class interfaceType : clazz.getInterfaces()) { + getErasedSuperTypeSet(interfaceType, destinationSet); + } + } + + return destinationSet; + } + + public JavaTypeDefinition getAsSuper(Class superClazz) { + if (clazz == superClazz) { // optimize for same class calls + return this; + } + + for (JavaTypeDefinition superTypeDef : getSuperTypeSet()) { + if (superTypeDef.getType() == superClazz) { + return superTypeDef; + } + } + + return null; + } + + public boolean isExactType() { + return getDefinitionType() == TypeDefinitionType.EXACT; + } + + public boolean isUpperBound() { + return getDefinitionType() == TypeDefinitionType.UPPER_BOUND + // intersection types can only be upper bounds in java + || getDefinitionType() == TypeDefinitionType.INTERSECTION; + } + + public boolean isLowerBound() { + return getDefinitionType() == TypeDefinitionType.LOWER_BOUND; + } + + public boolean isIntersectionType() { + return getDefinitionType() == TypeDefinitionType.INTERSECTION; + } + + public JavaTypeDefinition getIntersectionType(int index) { + throw new UnsupportedOperationException("Not an intersection type"); + } + + public int getIntersectionTypeCount() { + throw new UnsupportedOperationException("Not an intersection type"); + } + + public JavaTypeDefinition getLowerBound() { + if (!isLowerBound()) { + throw new UnsupportedOperationException("Not a lower bound type"); + } + + return lowerBound; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java index 7a147478a3..5fc2d675d4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java @@ -28,7 +28,7 @@ public enum InferenceRuleType { // If S and T are proper types, the constraint reduces to true if S is the same as T (ยง4.3.4), and false // otherwise. if (val.isLeftProper() && val.isRightProper()) { - if (val.leftProper().equivalent(val.rightProper())) { + if (val.leftProper().equals(val.rightProper())) { return newConstraints; } else { return null;