package pocketknife.internal.codegen; import javax.lang.model.element.Element; 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.util.Elements; import javax.lang.model.util.Types; import java.util.List; public class TypeUtil { private static final String SERIALIZABLE = "java.io.Serializable"; private static final String PARCELABLE = "android.os.Parcelable"; // private static final CharSequence BINDER = "android.os.IBinder"; // Api 18+ private static final String BUNDLE = "android.os.Bundle"; private static final String STRING = "java.lang.String"; private static final String CHAR_SEQUENCE = "java.lang.CharSequence"; private static final String INTEGER = "java.lang.Integer"; private static final String ARRAY_LIST = "java.util.ArrayList"; private static final String SPARSE_ARRAY = "android.util.SparseArray"; private static final String INTENT = "android.content.Intent"; private static final String CONTEXT = "android.content.Context"; private static final String BUILD = "android.os.Build"; private static final String URI = "android.net.Uri"; private static final String FRAGMENT = "android.app.Fragment"; private static final String SUPPORT_FRAGMENT = "android.support.v4.app.Fragment"; public final TypeMirror serializableType; public final TypeMirror parcelableType; // public final TypeMirror binderType; // API 18+ public final TypeMirror bundleType; public final TypeMirror stringType; public final TypeMirror charSequenceType; public final TypeMirror integerType; public final TypeMirror arrayListType; public final TypeMirror sparseArrayType; public final TypeMirror intentType; public final TypeMirror fragmentType; public final TypeMirror supportFragmentType; public final TypeMirror uriType; public final TypeMirror contextType; public final TypeMirror buildType; private static TypeUtil instance; private final Types types; public static synchronized TypeUtil getInstance(Elements elements, Types types) { if (instance == null) { instance = new TypeUtil(elements, types); } return instance; } private TypeUtil(Elements elements, Types types) { this.types = types; Element element = elements.getTypeElement(SERIALIZABLE); if (element == null) { throw new IllegalStateException("Unable to find Serializable type"); } serializableType = element.asType(); element = elements.getTypeElement(PARCELABLE); if (element == null) { throw new IllegalStateException("Unable to find Parcelable type"); } parcelableType = element.asType(); // element = elements.getTypeElement(BINDER) // if (element == null) { // throw new IllegalStateException("Unable to find Binder type") // } // binderType = element.asType() element = elements.getTypeElement(BUNDLE); if (element == null) { throw new IllegalStateException("Unable to find Bundle type"); } bundleType = element.asType(); element = elements.getTypeElement(STRING); if (element == null) { throw new IllegalStateException("Unable to find String type"); } stringType = element.asType(); element = elements.getTypeElement(CHAR_SEQUENCE); if (element == null) { throw new IllegalStateException("Unable to find CharSequence type"); } charSequenceType = element.asType(); element = elements.getTypeElement(INTEGER); if (element == null) { throw new IllegalStateException("Unable to find Integer type"); } integerType = element.asType(); element = elements.getTypeElement(ARRAY_LIST); if (element == null) { throw new IllegalStateException("Unable to find ArrayList type"); } arrayListType = element.asType(); element = elements.getTypeElement(SPARSE_ARRAY); if (element == null) { throw new IllegalStateException("Unable to find SparseArray type"); } sparseArrayType = element.asType(); element = elements.getTypeElement(INTENT); if (element == null) { throw new IllegalStateException("Unable to find Intent type"); } intentType = element.asType(); element = elements.getTypeElement(FRAGMENT); if (element == null) { throw new IllegalStateException("Unable to find Fragment type"); } fragmentType = element.asType(); element = elements.getTypeElement(SUPPORT_FRAGMENT); if (element == null) { throw new IllegalStateException("Unable to find support Fragment type"); } supportFragmentType = element.asType(); element = elements.getTypeElement(URI); if (element == null) { throw new IllegalStateException("Unable to find uri type"); } uriType = element.asType(); element = elements.getTypeElement(CONTEXT); if (element == null) { throw new IllegalStateException("Unable to find Context type"); } contextType = element.asType(); element = elements.getTypeElement(BUILD); if (element == null) { throw new IllegalStateException("Unable to find Build type"); } buildType = element.asType(); } public String getBundleType(TypeMirror type) throws InvalidTypeException { // Primitive if (isPrimitive(type)) { return getPrimitiveType(type); } // Array if (isArrayType(type)) { return getArrayType((ArrayType) type); } // ArrayList if (isArrayListType(type)) { return getArrayListType((DeclaredType) type); } // Sparse ParcelableArray if (isSparseParcelableArray(type)) { return "SparseParcelableArray"; } // Other types if (types.isAssignable(type, bundleType)) { return "Bundle"; } if (isAggregateType(type)) { return getAggregateType(type); } if (types.isAssignable(type, serializableType)) { return "Serializable"; } throw new InvalidTypeException(InvalidTypeException.Container.BUNDLE, type); } public String getIntentType(TypeMirror type) throws InvalidTypeException { // Primitive if (isPrimitive(type)) { return getPrimitiveType(type); } // Array if (isArrayType(type)) { return getArrayType((ArrayType) type); } // ArrayList if (isArrayListType(type)) { return getArrayListType((DeclaredType) type); } if (types.isAssignable(type, bundleType)) { return "Bundle"; } if (isAggregateType(type)) { return getAggregateType(type); } if (types.isAssignable(type, serializableType)) { return "Serializable"; } throw new InvalidTypeException(InvalidTypeException.Container.INTENT, type); } public boolean isPrimitive(TypeMirror type) { return type.getKind().isPrimitive(); } private boolean isAggregateType(TypeMirror type) { return types.isAssignable(type, stringType) || types.isAssignable(type, charSequenceType) || types.isAssignable(type, parcelableType); } private boolean isArrayType(TypeMirror type) { if (TypeKind.ARRAY == type.getKind() && type instanceof ArrayType) { TypeMirror componentType = ((ArrayType) type).getComponentType(); return isPrimitive(componentType) || isAggregateType(componentType); } return false; } private boolean isArrayListType(TypeMirror type) { if (types.isAssignable(types.erasure(type), arrayListType)) { List<? extends TypeMirror> typeArguments = ((DeclaredType) type).getTypeArguments(); if (typeArguments.size() == 1) { TypeMirror arg = typeArguments.get(0); if (types.isAssignable(arg, integerType)) { return true; } if (isAggregateType(arg)) { return true; } } } return false; } private boolean isSparseParcelableArray(TypeMirror type) { if (types.isAssignable(types.erasure(type), sparseArrayType) && type instanceof DeclaredType) { List<? extends TypeMirror> typeArguments = ((DeclaredType) type).getTypeArguments(); if (typeArguments.size() == 1) { return types.isAssignable(typeArguments.get(0), parcelableType); } } return false; } private String getPrimitiveType(TypeMirror type) throws InvalidTypeException { // No unboxing due to the nullable nature of boxed primitives switch (type.getKind()) { case BOOLEAN: return "Boolean"; case BYTE: return "Byte"; case SHORT: return "Short"; case INT: return "Int"; case LONG: return "Long"; case CHAR: return "Char"; case FLOAT: return "Float"; case DOUBLE: return "Double"; default: throw new InvalidTypeException(type); } } private String getArrayType(ArrayType type) throws InvalidTypeException { TypeMirror componentType = type.getComponentType(); if (isPrimitive(componentType)) { try { return getPrimitiveType(componentType).concat("Array"); } catch (InvalidTypeException e) { throw new InvalidTypeException(type); } } try { return getAggregateType(componentType).concat("Array"); } catch (InvalidTypeException e) { throw new InvalidTypeException(type); } } private String getArrayListType(DeclaredType type) throws InvalidTypeException { TypeMirror arg = type.getTypeArguments().get(0); if (types.isAssignable(arg, integerType)) { return "IntegerArrayList"; } try { return getAggregateType(arg).concat("ArrayList"); } catch (InvalidTypeException e) { throw new InvalidTypeException(type); } } private String getAggregateType(TypeMirror type) throws InvalidTypeException { if (types.isAssignable(type, stringType)) { // String is subtype of CharSequence should go first return "String"; } if (types.isAssignable(type, charSequenceType)) { return "CharSequence"; } if (types.isAssignable(type, parcelableType)) { return "Parcelable"; } throw new InvalidTypeException(type); } public boolean needToCastBundleType(TypeMirror type) throws InvalidTypeException { if (isPrimitive(type)) { return false; } if (isArrayType(type)) { return needToCastArrayType((ArrayType) type); } if (isArrayListType(type)) { return false; } if (isSparseParcelableArray(type)) { return needToCastSparseParcelableArray((DeclaredType) type); } if (types.isAssignable(type, bundleType)) { return !types.isSameType(type, bundleType); } if (isAggregateType(type)) { return needToCastAggregateType(type); } if (types.isAssignable(type, serializableType)) { return !types.isSameType(type, serializableType); } throw new InvalidTypeException(InvalidTypeException.Container.BUNDLE, type); } public boolean needToCastIntentType(TypeMirror type) throws InvalidTypeException { if (isPrimitive(type)) { return false; } if (isArrayType(type)) { return needToCastArrayType((ArrayType) type); } if (isArrayListType(type)) { return false; } if (types.isAssignable(type, bundleType)) { return !types.isSameType(type, bundleType); } if (isAggregateType(type)) { return needToCastAggregateType(type); } if (types.isAssignable(type, serializableType)) { return !types.isSameType(type, serializableType); } throw new InvalidTypeException(InvalidTypeException.Container.INTENT, type); } private boolean needToCastArrayType(ArrayType type) throws InvalidTypeException { TypeMirror componentType = type.getComponentType(); if (isPrimitive(componentType)) { return false; } try { return needToCastAggregateType(componentType); } catch (InvalidTypeException e) { throw new InvalidTypeException(type); } } private boolean needToCastAggregateType(TypeMirror type) throws InvalidTypeException { if (types.isAssignable(type, charSequenceType)) { return !types.isSameType(type, charSequenceType); } if (types.isAssignable(type, stringType)) { return !types.isSameType(type, stringType); } if (types.isAssignable(type, parcelableType)) { return !types.isSameType(type, parcelableType); } throw new InvalidTypeException(type); } private boolean needToCastSparseParcelableArray(DeclaredType type) { List<? extends TypeMirror> typeArguments = type.getTypeArguments(); return !types.isAssignable(typeArguments.get(0), parcelableType); } }