blob: fe7456f6d27990d992f517c5eaa63647cb15887a [file] [log] [blame]
package org.checkerframework.javacutil;
import static com.sun.tools.javac.code.TypeTag.WILDCARD;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.model.JavacTypes;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/** A utility class that helps with {@link TypeMirror}s. */
// TODO: This class needs significant restructuring
public final class TypesUtils {
// Class cannot be instantiated
private TypesUtils() {
throw new AssertionError("Class TypesUtils cannot be instantiated.");
}
/**
* Gets the fully qualified name for a provided type. It returns an empty name if type is an
* anonymous type.
*
* @param type the declared type
* @return the name corresponding to that type
*/
public static Name getQualifiedName(DeclaredType type) {
TypeElement element = (TypeElement) type.asElement();
return element.getQualifiedName();
}
/**
* Checks if the type represents a java.lang.Object declared type.
*
* @param type the type
* @return true iff type represents java.lang.Object
*/
public static boolean isObject(TypeMirror type) {
return isDeclaredOfName(type, "java.lang.Object");
}
/**
* Checks if the type represents a java.lang.Class declared type.
*
* @param type the type
* @return true iff type represents java.lang.Class
*/
public static boolean isClass(TypeMirror type) {
return isDeclaredOfName(type, "java.lang.Class");
}
/**
* Checks if the type represents a java.lang.String declared type. TODO: it would be cleaner to
* use String.class.getCanonicalName(), but the two existing methods above don't do that, I
* guess for performance reasons.
*
* @param type the type
* @return true iff type represents java.lang.String
*/
public static boolean isString(TypeMirror type) {
return isDeclaredOfName(type, "java.lang.String");
}
/**
* Checks if the type represents a boolean type, that is either boolean (primitive type) or
* java.lang.Boolean.
*
* @param type the type to test
* @return true iff type represents a boolean type
*/
public static boolean isBooleanType(TypeMirror type) {
return isDeclaredOfName(type, "java.lang.Boolean")
|| type.getKind().equals(TypeKind.BOOLEAN);
}
/**
* Check if the type represent a declared type of the given qualified name
*
* @param type the type
* @return type iff type represents a declared type of the qualified name
*/
public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) {
return type.getKind() == TypeKind.DECLARED
&& getQualifiedName((DeclaredType) type).contentEquals(qualifiedName);
}
public static boolean isBoxedPrimitive(TypeMirror type) {
if (type.getKind() != TypeKind.DECLARED) {
return false;
}
String qualifiedName = getQualifiedName((DeclaredType) type).toString();
return (qualifiedName.equals("java.lang.Boolean")
|| qualifiedName.equals("java.lang.Byte")
|| qualifiedName.equals("java.lang.Character")
|| qualifiedName.equals("java.lang.Short")
|| qualifiedName.equals("java.lang.Integer")
|| qualifiedName.equals("java.lang.Long")
|| qualifiedName.equals("java.lang.Double")
|| qualifiedName.equals("java.lang.Float"));
}
/** @return type represents a Throwable type (e.g. Exception, Error) */
public static boolean isThrowable(TypeMirror type) {
while (type != null && type.getKind() == TypeKind.DECLARED) {
DeclaredType dt = (DeclaredType) type;
TypeElement elem = (TypeElement) dt.asElement();
Name name = elem.getQualifiedName();
if ("java.lang.Throwable".contentEquals(name)) {
return true;
}
type = elem.getSuperclass();
}
return false;
}
/**
* Returns true iff the argument is an anonymous type.
*
* @return whether the argument is an anonymous type
*/
public static boolean isAnonymous(TypeMirror type) {
return (type instanceof DeclaredType)
&& (((TypeElement) ((DeclaredType) type).asElement())
.getNestingKind()
.equals(NestingKind.ANONYMOUS));
}
/**
* Returns true iff the argument is a primitive type.
*
* @return whether the argument is a primitive type
*/
public static boolean isPrimitive(TypeMirror type) {
switch (type.getKind()) {
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
return true;
default:
return false;
}
}
/**
* Returns true iff the arguments are both the same primitive types.
*
* @return whether the arguments are the same primitive types
*/
public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) {
if (!isPrimitive(left) || !isPrimitive(right)) {
return false;
}
return (left.getKind() == right.getKind());
}
/**
* Returns true iff the argument is a primitive numeric type.
*
* @return whether the argument is a primitive numeric type
*/
public static boolean isNumeric(TypeMirror type) {
switch (type.getKind()) {
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
return true;
default:
return false;
}
}
/**
* Returns true iff the argument is an integral type.
*
* @return whether the argument is an integral type
*/
public static boolean isIntegral(TypeMirror type) {
switch (type.getKind()) {
case BYTE:
case CHAR:
case INT:
case LONG:
case SHORT:
return true;
default:
return false;
}
}
/**
* Returns true iff the argument is a floating point type.
*
* @return whether the argument is a floating point type
*/
public static boolean isFloating(TypeMirror type) {
switch (type.getKind()) {
case DOUBLE:
case FLOAT:
return true;
default:
return false;
}
}
/**
* Returns the widened numeric type for an arithmetic operation performed on a value of the left
* type and the right type. Defined in JLS 5.6.2. We return a {@link TypeKind} because creating
* a {@link TypeMirror} requires a {@link Types} object from the {@link
* javax.annotation.processing.ProcessingEnvironment}.
*
* @return the result of widening numeric conversion, or NONE when the conversion cannot be
* performed
*/
public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) {
if (!isNumeric(left) || !isNumeric(right)) {
return TypeKind.NONE;
}
TypeKind leftKind = left.getKind();
TypeKind rightKind = right.getKind();
if (leftKind == TypeKind.DOUBLE || rightKind == TypeKind.DOUBLE) {
return TypeKind.DOUBLE;
}
if (leftKind == TypeKind.FLOAT || rightKind == TypeKind.FLOAT) {
return TypeKind.FLOAT;
}
if (leftKind == TypeKind.LONG || rightKind == TypeKind.LONG) {
return TypeKind.LONG;
}
return TypeKind.INT;
}
/**
* If the argument is a bounded TypeVariable or WildcardType, return its non-variable,
* non-wildcard upper bound. Otherwise, return the type itself.
*
* @param type a type
* @return the non-variable, non-wildcard upper bound of a type, if it has one, or itself if it
* has no bounds
*/
public static TypeMirror upperBound(TypeMirror type) {
do {
if (type instanceof TypeVariable) {
TypeVariable tvar = (TypeVariable) type;
if (tvar.getUpperBound() != null) {
type = tvar.getUpperBound();
} else {
break;
}
} else if (type instanceof WildcardType) {
WildcardType wc = (WildcardType) type;
if (wc.getExtendsBound() != null) {
type = wc.getExtendsBound();
} else {
break;
}
} else {
break;
}
} while (true);
return type;
}
/**
* Get the type parameter for this wildcard from the underlying type's bound field This field is
* sometimes null, in that case this method will return null
*
* @return the TypeParameterElement the wildcard is an argument to
*/
public static TypeParameterElement wildcardToTypeParam(final Type.WildcardType wildcard) {
final Element typeParamElement;
if (wildcard.bound != null) {
typeParamElement = wildcard.bound.asElement();
} else {
typeParamElement = null;
}
return (TypeParameterElement) typeParamElement;
}
/**
* Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) that works with both jdk8
* (called upperBound there) and jdk8u.
*/
// TODO: contrast to upperBound.
public static Type wildUpperBound(ProcessingEnvironment env, TypeMirror tm) {
Type t = (Type) tm;
if (t.hasTag(TypeTag.WILDCARD)) {
Context context = ((JavacProcessingEnvironment) env).getContext();
Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t);
if (w.isSuperBound()) {
Symtab syms = Symtab.instance(context);
return w.bound == null ? syms.objectType : w.bound.bound;
} else {
return wildUpperBound(env, w.type);
}
} else {
return TypeAnnotationUtils.unannotatedType(t);
}
}
/**
* Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) that works with both jdk8
* (called upperBound there) and jdk8u.
*/
public static Type wildLowerBound(ProcessingEnvironment env, TypeMirror tm) {
Type t = (Type) tm;
if (t.hasTag(WILDCARD)) {
Context context = ((JavacProcessingEnvironment) env).getContext();
Symtab syms = Symtab.instance(context);
Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t);
return w.isExtendsBound() ? syms.botType : wildLowerBound(env, w.type);
} else {
return TypeAnnotationUtils.unannotatedType(t);
}
}
/** Returns the {@link TypeMirror} for a given {@link Class}. */
public static TypeMirror typeFromClass(Types types, Elements elements, Class<?> clazz) {
if (clazz == void.class) {
return types.getNoType(TypeKind.VOID);
} else if (clazz.isPrimitive()) {
String primitiveName = clazz.getName().toUpperCase();
TypeKind primitiveKind = TypeKind.valueOf(primitiveName);
return types.getPrimitiveType(primitiveKind);
} else if (clazz.isArray()) {
TypeMirror componentType = typeFromClass(types, elements, clazz.getComponentType());
return types.getArrayType(componentType);
} else {
TypeElement element = elements.getTypeElement(clazz.getCanonicalName());
if (element == null) {
ErrorReporter.errorAbort("Unrecognized class: " + clazz);
return null; // dead code
}
return element.asType();
}
}
/** Returns an {@link ArrayType} with elements of type {@code componentType}. */
public static ArrayType createArrayType(Types types, TypeMirror componentType) {
JavacTypes t = (JavacTypes) types;
return t.getArrayType(componentType);
}
/**
* Returns true if declaredType is a Class that is used to box primitive type (e.g.
* declaredType=java.lang.Double and primitiveType=22.5d )
*/
public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) {
if (declaredType.getKind() != TypeKind.DECLARED) {
return false;
}
final String qualifiedName = getQualifiedName((DeclaredType) declaredType).toString();
switch (primitiveType.getKind()) {
case BOOLEAN:
return qualifiedName.equals("java.lang.Boolean");
case BYTE:
return qualifiedName.equals("java.lang.Byte");
case CHAR:
return qualifiedName.equals("java.lang.Character");
case DOUBLE:
return qualifiedName.equals("java.lang.Double");
case FLOAT:
return qualifiedName.equals("java.lang.Float");
case INT:
return qualifiedName.equals("java.lang.Integer");
case LONG:
return qualifiedName.equals("java.lang.Long");
case SHORT:
return qualifiedName.equals("java.lang.Short");
default:
return false;
}
}
/**
* Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If the
* bounded type extends other bounded types, this method will iterate through their bounds until
* a class, interface, or intersection is found.
*
* @return a type that is not a wildcard or typevar, or null if this type is an unbounded
* wildcard
*/
public static TypeMirror findConcreteUpperBound(final TypeMirror boundedType) {
TypeMirror effectiveUpper = boundedType;
outerLoop:
while (true) {
switch (effectiveUpper.getKind()) {
case WILDCARD:
effectiveUpper =
((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound();
if (effectiveUpper == null) {
return null;
}
break;
case TYPEVAR:
effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound();
break;
default:
break outerLoop;
}
}
return effectiveUpper;
}
/**
* Returns true if the erased type of subtype is a subtype of the erased type of supertype.
*
* @param types Types
* @param subtype possible subtype
* @param supertype possible supertype
* @return true if the erased type of subtype is a subtype of the erased type of supertype
*/
public static boolean isErasedSubtype(Types types, TypeMirror subtype, TypeMirror supertype) {
return types.isSubtype(types.erasure(subtype), types.erasure(supertype));
}
}