| 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)); |
| } |
| } |