com.vimeo.stag.processor.utils.TypeUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.vimeo.stag.processor.utils.TypeUtils.java

Source

/*
 * The MIT License (MIT)
 * <p/>
 * Copyright (c) 2016 Vimeo
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.vimeo.stag.processor.utils;

import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSerializer;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.vimeo.stag.processor.generators.model.accessor.FieldAccessor;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
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.WildcardType;
import javax.lang.model.util.Types;

public final class TypeUtils {

    private static final String TAG = TypeUtils.class.getSimpleName();

    @NotNull
    private static final HashMap<String, String> PRIMITIVE_TO_OBJECT_MAP = new HashMap<>();

    @Nullable
    private static Types sTypeUtils;

    static {
        PRIMITIVE_TO_OBJECT_MAP.put(boolean.class.getName(), Boolean.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(int.class.getName(), Integer.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(short.class.getName(), Short.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(double.class.getName(), Double.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(long.class.getName(), Long.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(float.class.getName(), Float.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(char.class.getName(), Character.class.getName());
        PRIMITIVE_TO_OBJECT_MAP.put(byte.class.getName(), Byte.class.getName());
    }

    private TypeUtils() {
        throw new UnsupportedOperationException("This class is not instantiable");
    }

    public static void initialize(@NotNull Types typeUtils) {
        sTypeUtils = typeUtils;
    }

    @NotNull
    private static Types getUtils() {
        Preconditions.checkNotNull(sTypeUtils);
        return sTypeUtils;
    }

    /**
     * Creates the full class name including package
     * name for the given class. Anonymous classes
     * and classes with the '$' character in their names
     * are not supported.
     *
     * @param clazz the class to get the name of.
     * @return the class's name, without any dollar
     * signs for inner classes and with periods instead.
     */
    @NotNull
    public static String className(@NotNull Class clazz) {
        return clazz.getName().replace('$', '.');
    }

    /**
     * Retrieves the outer type of a parameterized class.
     * e.g. an ArrayList{@literal <T>} would be returned as
     * just ArrayList. If an interface is passed in, i.e. a
     * List, the underlying implementation will be returned,
     * i.e. ArrayList.
     *
     * @param type the type to get the outer class from/
     * @return the outer class of the type passed in, or the
     * type itself if it is not parameterized.
     */
    @NotNull
    public static String getOuterClassType(@NotNull TypeMirror type) {
        if (type instanceof DeclaredType) {
            return ((DeclaredType) type).asElement().toString();
        } else {
            return type.toString();
        }
    }

    /**
     * Retrieves the outer type of a parameterized class.
     * e.g. an ArrayList{@literal <T>} would be returned as
     * just ArrayList. If an interface is passed in, i.e. a
     * List, the underlying implementation will be returned,
     * i.e. ArrayList.
     *
     * @param type the type to get the outer class from/
     * @return the outer class of the type passed in, or the
     * type itself if it is not parameterized.
     */
    @NotNull
    public static String getSimpleOuterClassType(@NotNull TypeMirror type) {
        if (type instanceof DeclaredType) {
            return ((DeclaredType) type).asElement().getSimpleName().toString();
        } else {
            return type.toString();
        }
    }

    /**
     * Determines whether or not the type has type parameters.
     *
     * @param type the type to check.
     * @return true if the type is not null and has type parameters,
     * false otherwise.
     */
    public static boolean isParameterizedType(@Nullable TypeMirror type) {
        List<? extends TypeMirror> typeArguments = getTypeArguments(type);
        return null != typeArguments && !typeArguments.isEmpty();
    }

    /**
     * Determines whether or not the type has type parameters.
     *
     * @param type the type to check.
     * @return true if the type is not null and has type parameters,
     * false otherwise.
     */
    @Nullable
    public static List<? extends TypeMirror> getTypeArguments(@Nullable TypeMirror type) {
        return type instanceof DeclaredType ? ((DeclaredType) type).getTypeArguments() : null;
    }

    /**
     * TypeMirrors should not be compared directly, but should use
     * {@link Types} in order to compare them.
     *
     * @param typeMirror1 the first type to compare.
     * @param typeMirror2 the second type to compare.
     * @return true if they are equal, false otherwise.
     */
    public static boolean areEqual(@Nullable TypeMirror typeMirror1, @Nullable TypeMirror typeMirror2) {
        if (typeMirror1 == null && typeMirror2 != null) {
            return false;
        } else if (typeMirror1 != null && typeMirror2 == null) {
            return false;
        } else if (typeMirror1 == typeMirror2) {
            return true;
        }
        return getUtils().isSameType(typeMirror1, typeMirror2);
    }

    /**
     * Determines whether or not the Element is a concrete type.
     * If the element is a generic type or contains generic type
     * arguments, this method will return false.
     *
     * @param element the element to check.
     * @return true if the element is not generic and
     * contains no generic type arguments, false otherwise.
     */
    public static boolean isConcreteType(@NotNull Element element) {
        return isConcreteType(element.asType());
    }

    /**
     * Determines whether or not the Element is a abstract type.
     *
     * @param element the element to check.
     * @return true if the element is abstract and
     * contains no generic type arguments, false otherwise.
     */
    public static boolean isAbstract(@Nullable Element element) {
        return element != null && element.getModifiers().contains(Modifier.ABSTRACT);
    }

    /**
     * Determines whether or not the Element is a abstract type.
     *
     * @param typeMirror the element to check.
     * @return true if the element is abstract and
     * contains no generic type arguments, false otherwise.
     */
    public static boolean isAbstract(@Nullable TypeMirror typeMirror) {
        return (typeMirror instanceof DeclaredType) && isAbstract(((DeclaredType) typeMirror).asElement());
    }

    /**
     * Determines whether or not the Element is a parameterized type.
     * If the element is a parameterized type or contains parameterized type
     * arguments, this method will return false.
     *
     * @param element the element to check.
     * @return true if the element is not generic and
     * contains no generic type arguments, false otherwise.
     */
    public static boolean isParameterizedType(@Nullable TypeElement element) {
        return element != null && isParameterizedType(element.asType());
    }

    /**
     * Determines whether or not the TypeMirror is a concrete type.
     * If the type is a generic type or contains generic type
     * arguments (i.e. a parameterized type), this method will
     * return false.
     *
     * @param typeMirror the element to check.
     * @return true if the type is not generic and
     * contains no generic type arguments, false otherwise.
     */
    public static boolean isConcreteType(@NotNull TypeMirror typeMirror) {
        if (typeMirror.getKind() == TypeKind.TYPEVAR) {
            return false;
        }
        if (isPrimitive(typeMirror, getUtils())) {
            return true;
        }
        if (typeMirror instanceof DeclaredType) {
            List<? extends TypeMirror> typeMirrors = ((DeclaredType) typeMirror).getTypeArguments();

            for (TypeMirror type : typeMirrors) {
                if (!isConcreteType(type)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Determines where the the type mirrors contains type var params or not
     *
     * @param typeMirror the element to check.
     * @return true if it contains type variables
     */
    public static boolean containsTypeVarParams(@NotNull TypeMirror typeMirror) {
        if (typeMirror.getKind() == TypeKind.TYPEVAR) {
            return true;
        }

        if (typeMirror instanceof DeclaredType) {
            List<? extends TypeMirror> typeMirrors = ((DeclaredType) typeMirror).getTypeArguments();

            for (TypeMirror type : typeMirrors) {
                if (containsTypeVarParams(type)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Gets the inherited type from the element. If
     * the inherited type is {@link Object} or {@link Enum},
     * then this method will return null.
     *
     * @param element the element to get the inherited type.
     * @return the inherited type, or null if the element
     * inherits from Object or Enum.
     */
    @Nullable
    public static TypeMirror getInheritedType(@Nullable TypeElement element) {
        TypeMirror typeMirror = element != null ? element.getSuperclass() : null;
        String className = typeMirror != null ? getClassNameFromTypeMirror(typeMirror) : null;
        if (!Object.class.getName().equals(className) && !Enum.class.getName().equals(className)) {
            return typeMirror;
        }
        return null;
    }

    /**
     * Determines whether the element is of the enum type or not.
     *
     * @param element the element to check.
     * @return true if the element inherits from an enum, false otherwise.
     */
    public static boolean isEnum(@Nullable TypeElement element) {
        TypeMirror typeMirror = element != null ? element.getSuperclass() : null;
        String className = typeMirror != null ? getClassNameFromTypeMirror(typeMirror) : null;

        return Enum.class.getName().equals(className);
    }

    /**
     * Retrieves a Map of the inherited concrete member variables of an Element. This takes all the
     * member variables that were inherited from the generic parent class and evaluates what their concrete
     * type will be based on the concrete inherited type. For instance, take the following code example:
     * <pre><code>
     * {@literal Factory<T>} {
     *
     *  {@literal @UseStag}
     *   public T data;
     *
     * }
     *
     * VideoFactory extends {@literal Factory<Video>}{
     *
     *   // other variables in here
     *
     * }
     * </code></pre>
     * In this example, VideoFactory has a public member variable T that is of type Video.
     * Since the Factory class has the UseStag annotation, we cannot just generate
     * parsing code for the Factory class, since it is generic and we need concrete types.
     * Instead when we generate the adapter for VideoFactory, we crawl the inheritance
     * hierarchy gathering the member variables. When we get to VideoFactory, we see it
     * has one member variable, T. We then look at the inherited type, Factory{@literal <Video>},
     * and compare it to the original type, Factory{@literal <T>}, and then infer the type
     * of T to be Video.
     *
     * @param concreteInherited the type inherited for the class you are using, in the example,
     *                          this would be Factory{@literal <Video>}
     * @param genericInherited  the raw type inherited for the class you are using, in the example,
     *                          this would be Factory{@literal <T>}
     * @param members           the member variable map of the field (Element) to their concrete
     *                          type (TypeMirror). This should be retrieved by calling getConcreteMembers
     *                          on the inherited class.
     * @return returns a LinkedHashMap of the member variables mapped to their concrete types for the concrete
     * inherited class. (to maintain the ordering)
     */
    @NotNull
    public static LinkedHashMap<FieldAccessor, TypeMirror> getConcreteMembers(@NotNull TypeMirror concreteInherited,
            @NotNull TypeElement genericInherited, @NotNull Map<FieldAccessor, TypeMirror> members) {

        DebugLog.log(TAG, "Inherited concrete type: " + concreteInherited.toString());
        DebugLog.log(TAG, "Inherited generic type: " + genericInherited.asType().toString());
        List<? extends TypeMirror> concreteTypes = getParameterizedTypes(concreteInherited);
        List<? extends TypeMirror> inheritedTypes = getParameterizedTypes(genericInherited);

        LinkedHashMap<FieldAccessor, TypeMirror> map = new LinkedHashMap<>();

        for (Entry<FieldAccessor, TypeMirror> member : members.entrySet()) {

            DebugLog.log(TAG, "\t\tEvaluating member - " + member.getValue().toString());

            if (isConcreteType(member.getValue())) {

                DebugLog.log(TAG, "\t\t\tConcrete Type: " + member.getValue().toString());
                map.put(member.getKey(), member.getValue());

            } else {

                if (isParameterizedType(member.getValue())) {

                    // HashMap<String, T> ...
                    TypeMirror resolvedType = resolveTypeVars(member.getValue(), inheritedTypes, concreteTypes);
                    map.put(member.getKey(), resolvedType);

                    DebugLog.log(TAG, "\t\t\tGeneric Parameterized Type - " + member.getValue().toString()
                            + " resolved to - " + resolvedType.toString());
                } else {

                    int index = inheritedTypes.indexOf(member.getKey().asType());
                    TypeMirror concreteType = concreteTypes.get(index);
                    map.put(member.getKey(), concreteType);

                    DebugLog.log(TAG, "\t\t\tGeneric Type - " + member.getValue().toString() + " resolved to - "
                            + concreteType.toString());
                }
            }
        }
        return map;
    }

    /**
     * Gets the primitive type mirror type.
     *
     * @param typeKind the type kind to get the primitive for.
     * @return the primitive type, or null if the type is not primitive.
     */
    @Nullable
    public static TypeMirror getPrimitive(@NotNull TypeKind typeKind) {
        try {
            return getUtils().getPrimitiveType(typeKind);
        } catch (Exception ignored) {
            return null;
        }
    }

    private static boolean isPrimitive(@NotNull TypeMirror type, @NotNull Types utils) {
        try {
            utils.getPrimitiveType(type.getKind());
            return true;
        } catch (IllegalArgumentException ignored) {
            return false;
        }
    }

    @NotNull
    private static TypeMirror resolveTypeVars(@NotNull TypeMirror element,
            @NotNull final List<? extends TypeMirror> inheritedTypes,
            @NotNull final List<? extends TypeMirror> concreteTypes) {
        if (isConcreteType(element)) {
            return element;
        }

        if (element.getKind() == TypeKind.TYPEVAR) {
            int index = inheritedTypes.indexOf(element);
            return concreteTypes.get(index);
        }

        Types types = getUtils();
        List<? extends TypeMirror> typeMirrors = ((DeclaredType) element).getTypeArguments();
        TypeElement typeElement = (TypeElement) types.asElement(element);
        List<TypeMirror> concreteGenericTypes = new ArrayList<>(typeMirrors.size());
        for (TypeMirror type : typeMirrors) {
            concreteGenericTypes.add(resolveTypeVars(type, inheritedTypes, concreteTypes));
        }
        TypeMirror[] concreteTypeArray = concreteGenericTypes.toArray(new TypeMirror[concreteGenericTypes.size()]);
        return types.getDeclaredType(typeElement, concreteTypeArray);
    }

    @NotNull
    private static List<? extends TypeMirror> getParameterizedTypes(@NotNull TypeElement element) {
        return ((DeclaredType) element.asType()).getTypeArguments();
    }

    @NotNull
    private static List<? extends TypeMirror> getParameterizedTypes(@NotNull TypeMirror typeMirror) {
        return ((DeclaredType) typeMirror).getTypeArguments();
    }

    /**
     * Method to check if the {@link TypeMirror} is of primitive type
     *
     * @param type :TypeMirror type
     * @return boolean
     */
    public static boolean isSupportedPrimitive(@NotNull String type) {
        return PRIMITIVE_TO_OBJECT_MAP.containsKey(type);
    }

    /**
     * Method to check if the {@link TypeMirror} is of primitive type
     *
     * @param type :TypeMirror type
     * @return String
     */
    public static String getObjectForPrimitive(@NotNull String type) {
        return PRIMITIVE_TO_OBJECT_MAP.get(type);
    }

    /**
     * Method to check if the {@link TypeMirror} is of {@link ArrayType}
     *
     * @param type :TypeMirror type
     * @return boolean
     */
    public static boolean isNativeArray(@NotNull TypeMirror type) {
        return (type instanceof ArrayType);
    }

    /**
     * Method to check if the {@link TypeMirror} is of {@link Collection} type
     *
     * @param type :TypeMirror type
     * @return boolean
     */
    public static boolean isSupportedCollection(@Nullable TypeMirror type) {
        return type != null && (isNativeArray(type) || isSupportedList(type));
    }

    /**
     * Method to check if the {@link TypeMirror} is of {@link List} type
     *
     * @param type :TypeMirror type
     * @return boolean
     */
    public static boolean isSupportedList(@Nullable TypeMirror type) {
        if (type == null) {
            return false;
        }
        String outerClassType = TypeUtils.getOuterClassType(type);
        return outerClassType.equals(ArrayList.class.getName()) || outerClassType.equals(List.class.getName())
                || outerClassType.equals(Collection.class.getName());
    }

    /**
     * Method to check if the {@link TypeMirror} is of {@link Object}
     *
     * @param type :TypeMirror type
     * @return boolean
     */
    public static boolean isNativeObject(@Nullable TypeMirror type) {
        if (type == null) {
            return false;
        }
        String outerClassType = TypeUtils.getOuterClassType(type);
        return outerClassType.equals(Object.class.getName());
    }

    /**
     * Method to check if the {@link TypeMirror} is of {@link Map} type
     *
     * @param type :TypeMirror type
     * @return boolean
     */
    public static boolean isSupportedMap(@Nullable TypeMirror type) {
        if (type == null) {
            return false;
        }
        String outerClassType = TypeUtils.getOuterClassType(type);
        return outerClassType.equals(Map.class.getName()) || outerClassType.equals(HashMap.class.getName())
                || outerClassType.equals(ConcurrentHashMap.class.getName())
                || outerClassType.equals("android.util.ArrayMap")
                || outerClassType.equals("android.support.v4.util.ArrayMap")
                || outerClassType.equals(LinkedHashMap.class.getName());
    }

    /**
     * Method to check if the type is natively supported such as {@link String} etc
     *
     * @param type String type
     * @return boolean
     */
    public static boolean isSupportedNative(@NotNull String type) {
        return isSupportedPrimitive(type) || type.equals(String.class.getName())
                || type.equals(Long.class.getName()) || type.equals(Integer.class.getName())
                || type.equals(Boolean.class.getName()) || type.equals(Double.class.getName())
                || type.equals(Float.class.getName()) || type.equals(Number.class.getName());
    }

    /**
     * Returns the inner {@link TypeMirror} for a given {@link TypeMirror}
     */
    @NotNull
    public static TypeMirror getArrayInnerType(@NotNull TypeMirror type) {
        return (type instanceof ArrayType) ? ((ArrayType) type).getComponentType()
                : ((DeclaredType) type).getTypeArguments().get(0);
    }

    @NotNull
    public static String getClassNameFromTypeMirror(@NotNull TypeMirror typeMirror) {
        String classAndPackage = typeMirror.toString();

        // This is done to avoid the generic template from being included in the file name
        // to be generated (since it will be an invalid file name)
        int idx = classAndPackage.indexOf("<");
        if (idx > 0) {
            classAndPackage = classAndPackage.substring(0, idx);
        }

        return classAndPackage;
    }

    /**
     * Convert the provided {@link TypeMirror} into an {@link Element} instance.  This method
     * call assumes that the provided {@link TypeMirror} is constrained to only known supported
     * types.  As a result it will guarantee non-null result values.
     *
     * @param typeMirror type mirror to convert
     * @return TypeElement representation of the type mirror
     */
    @NotNull
    public static TypeElement safeTypeMirrorToTypeElement(@NotNull TypeMirror typeMirror) {
        TypeElement element = unsafeTypeMirrorToTypeElement(typeMirror);
        // unsafeTypeMirrorToTypeElement may return null but not in the scenarios we are specifically using it for
        if (element == null) {
            throw new IllegalStateException("Supported type could not be converted into an Element");
        }
        return element;
    }

    /**
     * Convert the provided {@link TypeMirror} into an {@link Element} instance.
     *
     * @param typeMirror TypeMirror to convert
     * @return TypeElement representation of the TypeMirror
     */
    @Nullable
    public static TypeElement unsafeTypeMirrorToTypeElement(@NotNull TypeMirror typeMirror) {
        return (TypeElement) getUtils().asElement(typeMirror);
    }

    public enum JsonAdapterType {
        NONE, TYPE_ADAPTER, TYPE_ADAPTER_FACTORY, JSON_SERIALIZER, JSON_DESERIALIZER, JSON_SERIALIZER_DESERIALIZER

    }

    /**
     * Return the type of JsonAdapter {@link TypeMirror}
     *
     * @param type :TypeMirror type
     * @return {@link JsonAdapterType}
     */
    @NotNull
    public static JsonAdapterType getJsonAdapterType(@NotNull TypeMirror type) {
        Types types = getUtils();
        if (types.isSubtype(type, getDeclaredTypeForParameterizedClass(TypeAdapter.class.getName()))) {
            return JsonAdapterType.TYPE_ADAPTER;
        } else if (types.isAssignable(type,
                ElementUtils.getTypeFromQualifiedName(TypeAdapterFactory.class.getName()))) {
            return JsonAdapterType.TYPE_ADAPTER_FACTORY;
        } else {
            boolean isDeserializer = types.isSubtype(type,
                    getDeclaredTypeForParameterizedClass(JsonDeserializer.class.getName()));
            boolean isSerializer = types.isSubtype(type,
                    getDeclaredTypeForParameterizedClass(JsonSerializer.class.getName()));
            if (isSerializer && isDeserializer) {
                return JsonAdapterType.JSON_SERIALIZER_DESERIALIZER;
            } else if (isSerializer) {
                return JsonAdapterType.JSON_SERIALIZER;
            } else if (isDeserializer) {
                return JsonAdapterType.JSON_DESERIALIZER;
            } else {
                return JsonAdapterType.NONE;
            }
        }
    }

    public static boolean isAssignable(TypeMirror t1, TypeMirror t2) {
        return getUtils().isAssignable(t1, t2);
    }

    @NotNull
    public static DeclaredType getDeclaredTypeForParameterizedClass(@NotNull String className) {
        Types types = getUtils();
        WildcardType wildcardType = types.getWildcardType(null, null);
        TypeMirror[] typex = { wildcardType };
        return types.getDeclaredType(ElementUtils.getTypeElementFromQualifiedName(className), typex);
    }

    @NotNull
    public static DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) {
        Types types = getUtils();
        return types.getDeclaredType(typeElem, typeArgs);
    }

    public static boolean isWildcardType(@Nullable TypeMirror typeMirror) {
        return typeMirror instanceof WildcardType;
    }
}