/*
* Licensed 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 com.google.devtools.j2objc.util;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.j2objc.types.AbstractTypeMirror;
import com.google.devtools.j2objc.types.ExecutablePair;
import com.google.devtools.j2objc.types.GeneratedArrayType;
import com.google.devtools.j2objc.types.GeneratedTypeElement;
import com.google.devtools.j2objc.types.NativeType;
import com.google.devtools.j2objc.types.PointerType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
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.Types;
/**
* Utility methods for working with TypeMirrors.
*
* @author Nathan Braswell
*/
public final class TypeUtil {
public static final TypeMirror ID_TYPE = new NativeType("id");
public static final TypeMirror ID_PTR_TYPE = new PointerType(ID_TYPE);
public static final TypeElement NS_OBJECT =
GeneratedTypeElement.newIosClass("NSObject", null, "");
public static final TypeElement NS_STRING =
GeneratedTypeElement.newIosClass("NSString", NS_OBJECT, "");
public static final TypeElement NS_EXCEPTION =
GeneratedTypeElement.newIosClass("NSException", NS_OBJECT, "");
public static final TypeElement NS_NUMBER =
GeneratedTypeElement.newIosClass("NSNumber", NS_OBJECT, "");
public static final TypeElement IOS_CLASS =
GeneratedTypeElement.newIosClass("IOSClass", NS_OBJECT, "IOSClass.h");
public static final TypeElement NS_COPYING =
GeneratedTypeElement.newIosInterface("NSCopying", "");
public static final TypeElement IOS_OBJECT_ARRAY =
GeneratedTypeElement.newIosClass("IOSObjectArray", NS_OBJECT, "IOSObjectArray.h");
public static final TypeMirror NATIVE_CHAR_PTR = new NativeType("char *");
private static final Map<TypeKind, TypeElement> PRIMITIVE_IOS_ARRAYS;
static {
Map<TypeKind, TypeElement> map = new EnumMap<>(TypeKind.class);
map.put(TypeKind.BOOLEAN, newPrimitiveIosArray("IOSBooleanArray"));
map.put(TypeKind.BYTE, newPrimitiveIosArray("IOSByteArray"));
map.put(TypeKind.CHAR, newPrimitiveIosArray("IOSCharArray"));
map.put(TypeKind.DOUBLE, newPrimitiveIosArray("IOSDoubleArray"));
map.put(TypeKind.FLOAT, newPrimitiveIosArray("IOSFloatArray"));
map.put(TypeKind.INT, newPrimitiveIosArray("IOSIntArray"));
map.put(TypeKind.LONG, newPrimitiveIosArray("IOSLongArray"));
map.put(TypeKind.SHORT, newPrimitiveIosArray("IOSShortArray"));
PRIMITIVE_IOS_ARRAYS = map;
}
private final ParserEnvironment env;
private final Types javacTypes;
private final ElementUtil elementUtil;
// Commonly accessed types.
private final TypeElement javaObject;
private final TypeElement javaString;
private final TypeElement javaClass;
private final TypeElement javaNumber;
private final Map<TypeElement, TypeElement> javaToObjcTypeMap;
private static final Joiner INNER_CLASS_JOINER = Joiner.on('$');
public TypeUtil(ParserEnvironment env, ElementUtil elementUtil) {
this.env = env;
this.javacTypes = env.typeUtilities();
this.elementUtil = elementUtil;
javaObject = (TypeElement) env.resolve("java.lang.Object");
javaString = (TypeElement) env.resolve("java.lang.String");
javaClass = (TypeElement) env.resolve("java.lang.Class");
javaNumber = (TypeElement) env.resolve("java.lang.Number");
TypeElement javaThrowable = (TypeElement) env.resolve("java.lang.Throwable");
TypeElement javaCloneable = (TypeElement) env.resolve("java.lang.Cloneable");
javaToObjcTypeMap = ImmutableMap.<TypeElement, TypeElement>builder()
.put(javaObject, NS_OBJECT)
.put(javaString, NS_STRING)
.put(javaClass, IOS_CLASS)
.put(javaNumber, NS_NUMBER)
.put(javaThrowable, NS_EXCEPTION)
.put(javaCloneable, NS_COPYING)
.build();
}
public ElementUtil elementUtil() {
return elementUtil;
}
public TypeElement resolveJavaType(String qualifiedName) {
return (TypeElement) env.resolve(qualifiedName);
}
public static boolean isDeclaredType(TypeMirror t) {
return t.getKind() == TypeKind.DECLARED;
}
public static ElementKind getDeclaredTypeKind(TypeMirror t) {
return isDeclaredType(t) ? ((DeclaredType) t).asElement().getKind() : null;
}
public static boolean isClass(TypeMirror t) {
ElementKind kind = getDeclaredTypeKind(t);
return kind != null ? kind.isClass() : false;
}
public static boolean isInterface(TypeMirror t) {
ElementKind kind = getDeclaredTypeKind(t);
return kind != null ? kind.isInterface() : false;
}
public static boolean isEnum(TypeMirror t) {
return getDeclaredTypeKind(t) == ElementKind.ENUM;
}
public static boolean isAnnotation(TypeMirror t) {
return getDeclaredTypeKind(t) == ElementKind.ANNOTATION_TYPE;
}
public static boolean isBoolean(TypeMirror t) {
return t.getKind() == TypeKind.BOOLEAN;
}
public static boolean isVoid(TypeMirror t) {
return t.getKind() == TypeKind.VOID;
}
public static boolean isPrimitiveOrVoid(TypeMirror t) {
return isVoid(t) || t.getKind().isPrimitive();
}
public static boolean isNone(TypeMirror t) {
// Check for null because BindingConverter converts null bindings to null types.
return t == null || t.getKind() == TypeKind.NONE;
}
public static boolean isArray(TypeMirror t) {
return t.getKind() == TypeKind.ARRAY;
}
public static boolean isFloatingPoint(TypeMirror t) {
TypeKind kind = t.getKind();
return kind == TypeKind.FLOAT || kind == TypeKind.DOUBLE;
}
public static boolean isIntersection(TypeMirror t) {
return t.getKind() == TypeKind.INTERSECTION;
}
public static boolean isTypeVariable(TypeMirror t) {
return t.getKind() == TypeKind.TYPEVAR;
}
public static TypeElement asTypeElement(TypeMirror t) {
return isDeclaredType(t) ? (TypeElement) ((DeclaredType) t).asElement() : null;
}
public static TypeParameterElement asTypeParameterElement(TypeMirror t) {
return isTypeVariable(t) ? (TypeParameterElement) ((TypeVariable) t).asElement() : null;
}
public DeclaredType getSuperclass(TypeMirror t) {
List<? extends TypeMirror> supertypes = directSupertypes(t);
if (supertypes.isEmpty()) {
return null;
}
TypeMirror first = supertypes.get(0);
if (getDeclaredTypeKind(first).isClass()) {
return (DeclaredType) first;
}
return null;
}
public static int getDimensions(ArrayType arrayType) {
int dimCount = 0;
TypeMirror t = arrayType;
while (t.getKind().equals(TypeKind.ARRAY)) {
dimCount++;
t = (((ArrayType) t).getComponentType());
}
return dimCount;
}
public ExecutableType asMemberOf(DeclaredType containing, ExecutableElement method) {
return (ExecutableType) javacTypes.asMemberOf(containing, method);
}
public TypeMirror asMemberOf(DeclaredType containing, VariableElement var) {
return javacTypes.asMemberOf(containing, var);
}
public boolean isAssignable(TypeMirror t1, TypeMirror t2) {
return javacTypes.isAssignable(t1, t2);
}
public boolean isSubtype(TypeMirror t1, TypeMirror t2) {
return javacTypes.isSubtype(t1, t2);
}
public boolean isSubsignature(ExecutableType m1, ExecutableType m2) {
if (isGeneratedType(m1) || isGeneratedType(m2)) {
return m1.equals(m2);
}
return javacTypes.isSubsignature(m1, m2);
}
public List<? extends TypeMirror> directSupertypes(TypeMirror t) {
if (isGeneratedType(t)) {
if (t instanceof GeneratedTypeElement.Mirror) {
GeneratedTypeElement element = (GeneratedTypeElement)
((GeneratedTypeElement.Mirror) t).asElement();
return element.getDirectSupertypes();
} else {
return Collections.emptyList();
}
}
if (isArray(t)) {
// javac's directSupertypes() for String[] is Object[], whereas
// JDT's returns an empty list. Currently typed arrays aren't necessary,
// so prefer the JDT behavior here.
return Collections.emptyList();
}
List<? extends TypeMirror> result = new ArrayList<>(javacTypes.directSupertypes(t));
if (TypeUtil.isInterface(t)) {
result.remove(javaObject.asType());
}
return result;
}
public TypeMirror erasure(TypeMirror t) {
return javacTypes.erasure(t);
}
public ArrayType getArrayType(TypeMirror componentType) {
if (isGeneratedType(componentType)) {
return new GeneratedArrayType(componentType);
}
return javacTypes.getArrayType(componentType);
}
boolean isGeneratedType(TypeMirror type) {
return type instanceof AbstractTypeMirror;
}
public TypeElement getIosArray(TypeMirror componentType) {
return componentType.getKind().isPrimitive()
? PRIMITIVE_IOS_ARRAYS.get(componentType.getKind()) : IOS_OBJECT_ARRAY;
}
public TypeElement getJavaObject() {
return javaObject;
}
public TypeElement getJavaString() {
return javaString;
}
public TypeElement getJavaClass() {
return javaClass;
}
public TypeElement getJavaNumber() {
return javaNumber;
}
public boolean isString(TypeElement e) {
return javaString.equals(e) || NS_STRING.equals(e);
}
public boolean isString(TypeMirror t) {
return isString(asTypeElement(t));
}
public boolean isClassType(TypeElement e) {
return javaClass.equals(e) || IOS_CLASS.equals(e);
}
public boolean isClassType(TypeMirror t) {
return isClassType(asTypeElement(t));
}
/**
* Maps the given type to it's Objective-C equivalent. Array types are mapped to their equivalent
* IOSArray type and common Java classes like String and Object are mapped to NSString and
* NSObject.
*/
public TypeElement getObjcClass(TypeMirror t) {
if (isArray(t)) {
return getIosArray(((ArrayType) t).getComponentType());
} else if (isDeclaredType(t)) {
return getObjcClass((TypeElement) ((DeclaredType) t).asElement());
}
return null;
}
public TypeElement getObjcClass(TypeElement element) {
TypeElement mapped = javaToObjcTypeMap.get(element);
return mapped != null ? mapped : element;
}
/**
* Find a supertype matching the given qualified name.
*/
public DeclaredType findSupertype(TypeMirror type, String qualifiedName) {
TypeElement element = asTypeElement(type);
if (element != null && element.getQualifiedName().toString().equals(qualifiedName)) {
return (DeclaredType) type;
}
for (TypeMirror t : directSupertypes(type)) {
DeclaredType result = findSupertype(t, qualifiedName);
if (result != null) {
return result;
}
}
return null;
}
public ExecutablePair findMethod(DeclaredType type, String name, String... paramTypes) {
ExecutableElement methodElem =
ElementUtil.findMethod((TypeElement) type.asElement(), name, paramTypes);
if (methodElem != null) {
return new ExecutablePair(methodElem, asMemberOf(type, methodElem));
}
return null;
}
public LinkedHashSet<DeclaredType> getObjcOrderedInheritedTypes(TypeMirror type) {
LinkedHashSet<DeclaredType> inheritedTypes = new LinkedHashSet<>();
visitTypeHierarchyObjcOrder(type, visitType -> {
inheritedTypes.add(visitType);
return true;
});
return inheritedTypes;
}
/**
* Visitor type for calling the methods below. Return true to continue visiting, false to
* short-circuit.
*/
public interface TypeVisitor {
boolean accept(DeclaredType type);
}
/**
* Visit all declared types in the hierarchy using a depth-first traversal, visiting classes
* before interfaces.
*/
public boolean visitTypeHierarchy(TypeMirror type, TypeVisitor visitor) {
boolean result = true;
if (type == null) {
return result;
}
if (type.getKind() == TypeKind.DECLARED) {
result = visitor.accept((DeclaredType) type);
}
for (TypeMirror superType : directSupertypes(type)) {
if (!result) {
return false;
}
result = visitTypeHierarchy(superType, visitor);
}
return result;
}
/**
* Visit all declared types in the order that Objective-C compilation will visit when resolving
* the type signature of a method. Uses a depth-first traversal, visiting interfaces before
* classes.
*/
public boolean visitTypeHierarchyObjcOrder(TypeMirror type, TypeVisitor visitor) {
boolean result = true;
if (type == null) {
return result;
}
if (type.getKind() == TypeKind.DECLARED) {
result = visitor.accept((DeclaredType) type);
}
// Visit the class type after interface types which is the order the ObjC compiler visits the
// hierarchy.
TypeMirror classType = null;
for (TypeMirror superType : directSupertypes(type)) {
if (!result) {
return false;
}
if (isClass(superType)) {
classType = superType;
} else {
visitTypeHierarchyObjcOrder(superType, visitor);
}
}
if (classType != null && result) {
result = visitTypeHierarchyObjcOrder(classType, visitor);
}
return result;
}
public static boolean isReferenceType(TypeMirror t) {
switch (t.getKind()) {
case OTHER:
return isId(t);
case ARRAY:
case DECLARED:
case ERROR:
case INTERSECTION:
case NULL:
case TYPEVAR:
case UNION:
case WILDCARD:
return true;
default:
return false;
}
}
public static boolean isId(TypeMirror t) {
return t instanceof NativeType && ((NativeType) t).getName().equals("id");
}
public PrimitiveType getPrimitiveType(TypeKind kind) {
return javacTypes.getPrimitiveType(kind);
}
public PrimitiveType getBoolean() {
return getPrimitiveType(TypeKind.BOOLEAN);
}
public PrimitiveType getByte() {
return getPrimitiveType(TypeKind.BYTE);
}
public PrimitiveType getChar() {
return getPrimitiveType(TypeKind.CHAR);
}
public PrimitiveType getDouble() {
return getPrimitiveType(TypeKind.DOUBLE);
}
public PrimitiveType getFloat() {
return getPrimitiveType(TypeKind.FLOAT);
}
public PrimitiveType getInt() {
return getPrimitiveType(TypeKind.INT);
}
public PrimitiveType getLong() {
return getPrimitiveType(TypeKind.LONG);
}
public PrimitiveType getShort() {
return getPrimitiveType(TypeKind.SHORT);
}
public NoType getVoid() {
return javacTypes.getNoType(TypeKind.VOID);
}
public NullType getNull() {
return javacTypes.getNullType();
}
public PrimitiveType unboxedType(TypeMirror t) {
if (isGeneratedType(t)) {
return null;
}
try {
return javacTypes.unboxedType(t);
} catch (IllegalArgumentException e) {
return null;
}
}
public boolean isBoxedType(TypeMirror t) {
return unboxedType(t) != null;
}
public TypeElement boxedClass(PrimitiveType t) {
return javacTypes.boxedClass(t);
}
public boolean isDeclaredAsId(TypeMirror t) {
return isReferenceType(t) && getObjcUpperBounds(t).isEmpty();
}
public boolean isObjcAssignable(TypeMirror t1, TypeMirror t2) {
if (!isReferenceType(t1) || !isReferenceType(t2)) {
if (t1 instanceof PointerType && t2 instanceof PointerType) {
return isObjcAssignable(((PointerType) t1).getPointeeType(),
((PointerType) t2).getPointeeType());
}
return t1.equals(t2);
}
outer: for (TypeElement t2Class : getObjcUpperBounds(t2)) {
for (TypeElement t1Class : getObjcUpperBounds(t1)) {
if (isObjcSubtype(t1Class, t2Class)) {
continue outer;
}
}
return false;
}
return true;
}
public boolean isObjcSubtype(TypeElement type, TypeElement targetSupertype) {
if (type == null) {
return false;
}
if (type.equals(targetSupertype)) {
return true;
}
TypeMirror superclass = type.getSuperclass();
if (superclass != null && isObjcSubtype(getObjcClass(superclass), targetSupertype)) {
return true;
}
for (TypeMirror intrface : type.getInterfaces()) {
if (isObjcSubtype(getObjcClass(intrface), targetSupertype)) {
return true;
}
}
return false;
}
public List<TypeElement> getObjcUpperBounds(TypeMirror t) {
List<TypeElement> result = new ArrayList<>();
for (TypeMirror bound : getUpperBounds(t)) {
TypeElement elem = getObjcClass(bound);
// NSObject is emmitted as "id".
if (elem != null && !elem.equals(NS_OBJECT)) {
result.add(elem);
}
}
return result;
}
public List<? extends TypeMirror> getUpperBounds(TypeMirror t) {
if (t == null) {
return Collections.singletonList(env.resolve("java.lang.Object").asType());
}
switch (t.getKind()) {
case INTERSECTION:
return ((IntersectionType) t).getBounds();
case TYPEVAR:
return getUpperBounds(((TypeVariable) t).getUpperBound());
case WILDCARD:
return getUpperBounds(((WildcardType) t).getExtendsBound());
default:
return Collections.singletonList(t);
}
}
public static String getName(TypeMirror t) {
switch (t.getKind()) {
case ARRAY:
return getName(((ArrayType) t).getComponentType()) + "[]";
case DECLARED:
return ElementUtil.getName(asTypeElement(t));
case TYPEVAR:
return ElementUtil.getName(((TypeVariable) t).asElement());
case BOOLEAN:
return "boolean";
case BYTE:
return "byte";
case CHAR:
return "char";
case DOUBLE:
return "double";
case FLOAT:
return "float";
case INT:
return "int";
case LONG:
return "long";
case SHORT:
return "short";
case VOID:
return "void";
default:
throw new AssertionError("Cannot resolve name for type: " + t);
}
}
public static String getQualifiedName(TypeMirror t) {
switch (t.getKind()) {
case ARRAY:
return "[" + getQualifiedName(((ArrayType) t).getComponentType());
case DECLARED:
return asTypeElement(t).getQualifiedName().toString();
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
case VOID:
return getName(t);
default:
throw new AssertionError("Cannot resolve qualified name for type: " + t);
}
}
public String getSignatureName(TypeMirror t) {
t = erasure(t);
switch (t.getKind()) {
case ARRAY:
return "[" + getSignatureName(((ArrayType) t).getComponentType());
case DECLARED:
return "L" + elementUtil.getBinaryName(asTypeElement(t)).replace('.', '/') + ";";
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
case VOID:
return getBinaryName(t);
default:
throw new AssertionError("Cannot resolve signature name for type: " + t);
}
}
/**
* Returns the binary name for a primitive or void type.
*/
public static String getBinaryName(TypeMirror t) {
switch (t.getKind()) {
case BOOLEAN: return "Z";
case BYTE: return "B";
case CHAR: return "C";
case DOUBLE: return "D";
case FLOAT: return "F";
case INT: return "I";
case LONG: return "J";
case SHORT: return "S";
case VOID: return "V";
default:
throw new AssertionError("Cannot resolve binary name for type: " + t);
}
}
/**
* Get the "Reference" name of a method.
* For non-constructors this is the method's name.
* For constructors of top-level classes, this is the name of the class.
* For constructors of inner classes, this is the $-delimited name path
* from the outermost class declaration to the inner class declaration.
*/
public String getReferenceName(ExecutableElement element) {
if (!ElementUtil.isConstructor(element)) {
return ElementUtil.getName(element);
}
TypeElement parent = ElementUtil.getDeclaringClass(element);
assert parent != null;
List<String> components = new LinkedList<>(); // LinkedList is faster for prepending.
do {
components.add(0, ElementUtil.getName(parent));
parent = ElementUtil.getDeclaringClass(parent);
} while (parent != null);
return INNER_CLASS_JOINER.join(components);
}
/**
* Get the "Reference" signature of a method.
*/
public String getReferenceSignature(ExecutableElement element) {
StringBuilder sb = new StringBuilder("(");
// If the method is an inner class constructor, prepend the outer class type.
if (ElementUtil.isConstructor(element)) {
TypeElement declaringClass = ElementUtil.getDeclaringClass(element);
if (ElementUtil.hasOuterContext(declaringClass)) {
TypeElement outerClass = ElementUtil.getDeclaringClass(declaringClass);
sb.append(getSignatureName(outerClass.asType()));
}
}
for (VariableElement param : element.getParameters()) {
sb.append(getSignatureName(param.asType()));
}
sb.append(')');
TypeMirror returnType = element.getReturnType();
if (returnType != null) {
sb.append(getSignatureName(returnType));
}
return sb.toString();
}
private static TypeElement newPrimitiveIosArray(String name) {
return GeneratedTypeElement.newIosClass(name, NS_OBJECT, "IOSPrimitiveArray.h");
}
}