org.apache.beam.sdk.values.TypeDescriptor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.beam.sdk.values.TypeDescriptor.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.beam.sdk.values;

import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeResolver;
import com.google.common.reflect.TypeToken;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.List;
import javax.annotation.Nullable;

/**
 * A description of a Java type, including actual generic parameters where possible.
 *
 * <p>To prevent losing actual type arguments due to erasure, create an anonymous subclass
 * with concrete types:
 * <pre>
 * {@code
 * TypeDecriptor<List<String>> = new TypeDescriptor<List<String>>() {};
 * }
 * </pre>
 *
 * <p>If the above were not an anonymous subclass, the type {@code List<String>}
 * would be erased and unavailable at run time.
 *
 * @param <T> the type represented by this {@link TypeDescriptor}
 */
public abstract class TypeDescriptor<T> implements Serializable {

    // This class is just a wrapper for TypeToken
    private final TypeToken<T> token;

    /**
     * Creates a {@link TypeDescriptor} wrapping the provided token.
     * This constructor is private so Guava types do not leak.
     */
    private TypeDescriptor(TypeToken<T> token) {
        this.token = token;
    }

    /**
     * Creates a {@link TypeDescriptor} representing
     * the type parameter {@code T}. To use this constructor
     * properly, the type parameter must be a concrete type, for example
     * {@code new TypeDescriptor<List<String>>(){}}.
     */
    protected TypeDescriptor() {
        token = new TypeToken<T>(getClass()) {
        };
    }

    /**
     * Creates a {@link TypeDescriptor} representing the type parameter {@code T}, which should
     * resolve to a concrete type in the context of the class {@code clazz}.
     *
     * <p>Unlike {@link TypeDescriptor#TypeDescriptor(Class)} this will also use context's of the
     * enclosing instances while attempting to resolve the type. This means that the types of any
     * classes instantiated in the concrete instance should be resolvable.
     */
    protected TypeDescriptor(Object instance) {
        TypeToken<?> unresolvedToken = new TypeToken<T>(getClass()) {
        };

        // While we haven't fully resolved the parameters, refine it using the captured
        // enclosing instance of the object.
        unresolvedToken = TypeToken.of(instance.getClass()).resolveType(unresolvedToken.getType());

        if (hasUnresolvedParameters(unresolvedToken.getType())) {
            for (Field field : instance.getClass().getDeclaredFields()) {
                Object fieldInstance = getEnclosingInstance(field, instance);
                if (fieldInstance != null) {
                    unresolvedToken = TypeToken.of(fieldInstance.getClass()).resolveType(unresolvedToken.getType());
                    if (!hasUnresolvedParameters(unresolvedToken.getType())) {
                        break;
                    }
                }
            }
        }

        // Once we've either fully resolved the parameters or exhausted enclosing instances, we have
        // the best approximation to the token we can get.
        @SuppressWarnings("unchecked")
        TypeToken<T> typedToken = (TypeToken<T>) unresolvedToken;
        token = typedToken;
    }

    private boolean hasUnresolvedParameters(Type type) {
        if (type instanceof TypeVariable) {
            return true;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType param = (ParameterizedType) type;
            for (Type arg : param.getActualTypeArguments()) {
                if (hasUnresolvedParameters(arg)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns the enclosing instance if the field is synthetic and it is able to access it, or
     * {@literal null} if not.
     */
    @Nullable
    private Object getEnclosingInstance(Field field, Object instance) {
        if (!field.isSynthetic()) {
            return null;
        }

        boolean accessible = field.isAccessible();
        try {
            field.setAccessible(true);
            return field.get(instance);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            // If we fail to get the enclosing instance field, do nothing. In the worst case, we won't
            // refine the type based on information in this enclosing class -- that is consistent with
            // previous behavior and is still a correct answer that can be fixed by returning the correct
            // type descriptor.
            return null;
        } finally {
            field.setAccessible(accessible);
        }
    }

    /**
     * Creates a {@link TypeDescriptor} representing the type parameter
     * {@code T}, which should resolve to a concrete type in the context
     * of the class {@code clazz}.
     */
    @SuppressWarnings("unchecked")
    protected TypeDescriptor(Class<?> clazz) {
        TypeToken<T> unresolvedToken = new TypeToken<T>(getClass()) {
        };
        token = (TypeToken<T>) TypeToken.of(clazz).resolveType(unresolvedToken.getType());
    }

    /**
     * Returns a {@link TypeDescriptor} representing the given type.
     */
    public static <T> TypeDescriptor<T> of(Class<T> type) {
        return new SimpleTypeDescriptor<>(TypeToken.<T>of(type));
    }

    /**
     * Returns a {@link TypeDescriptor} representing the given type.
     */
    @SuppressWarnings("unchecked")
    public static TypeDescriptor<?> of(Type type) {
        return new SimpleTypeDescriptor<>((TypeToken<Object>) TypeToken.of(type));
    }

    /**
     * Returns the {@link Type} represented by this {@link TypeDescriptor}.
     */
    public Type getType() {
        return token.getType();
    }

    /**
     * Returns the {@link Class} underlying the {@link Type} represented by
     * this {@link TypeDescriptor}.
     */
    public Class<? super T> getRawType() {
        return token.getRawType();
    }

    /**
     * Returns the component type if this type is an array type,
     * otherwise returns {@code null}.
     */
    public TypeDescriptor<?> getComponentType() {
        return new SimpleTypeDescriptor<>(token.getComponentType());
    }

    /**
     * Returns the generic form of a supertype.
     */
    public final TypeDescriptor<? super T> getSupertype(Class<? super T> superclass) {
        return new SimpleTypeDescriptor<>(token.getSupertype(superclass));
    }

    /**
     * Returns true if this type is known to be an array type.
     */
    public final boolean isArray() {
        return token.isArray();
    }

    /**
     * Returns a {@link TypeVariable} for the named type parameter. Throws
     * {@link IllegalArgumentException} if a type variable by the requested type parameter is not
     * found.
     *
     * <p>For example, {@code new TypeDescriptor<List>(){}.getTypeParameter("T")} returns a
     * {@code TypeVariable<? super List>} representing the formal type parameter {@code T}.
     *
     * <p>Do not mistake the type parameters (formal type argument list) with the actual
     * type arguments. For example, if a class {@code Foo} extends {@code List<String>}, it
     * does not make sense to ask for a type parameter, because {@code Foo} does not have any.
     */
    public final TypeVariable<Class<? super T>> getTypeParameter(String paramName) {
        // Cannot convert TypeVariable<Class<? super T>>[] to TypeVariable<Class<? super T>>[]
        // due to how they are used here, so the result of getTypeParameters() cannot be used
        // without upcast.
        Class<?> rawType = getRawType();
        for (TypeVariable<?> param : rawType.getTypeParameters()) {
            if (param.getName().equals(paramName)) {
                @SuppressWarnings("unchecked")
                TypeVariable<Class<? super T>> typedParam = (TypeVariable<Class<? super T>>) param;
                return typedParam;
            }
        }
        throw new IllegalArgumentException("No type parameter named " + paramName + " found on " + getRawType());
    }

    /**
     * Returns true if this type is assignable from the given type.
     */
    public final boolean isSupertypeOf(TypeDescriptor<?> source) {
        return token.isSupertypeOf(source.token);
    }

    /**
     * Return true if this type is a subtype of the given type.
     */
    public final boolean isSubtypeOf(TypeDescriptor<?> parent) {
        return token.isSubtypeOf(parent.token);
    }

    /**
     * Returns a list of argument types for the given method, which must
     * be a part of the class.
     */
    public List<TypeDescriptor<?>> getArgumentTypes(Method method) {
        Invokable<?, ?> typedMethod = token.method(method);

        List<TypeDescriptor<?>> argTypes = Lists.newArrayList();
        for (Parameter parameter : typedMethod.getParameters()) {
            argTypes.add(new SimpleTypeDescriptor<>(parameter.getType()));
        }
        return argTypes;
    }

    /**
     * Returns a {@link TypeDescriptor} representing the given
     * type, with type variables resolved according to the specialization
     * in this type.
     *
     * <p>For example, consider the following class:
     * <pre>
     * {@code
     * class MyList implements List<String> { ... }
     * }
     * </pre>
     *
     * <p>The {@link TypeDescriptor} returned by
     * <pre>
     * {@code
     * TypeDescriptor.of(MyList.class)
     *     .resolveType(Mylist.class.getMethod("get", int.class).getGenericReturnType)
     * }
     * </pre>
     * will represent the type {@code String}.
     */
    public TypeDescriptor<?> resolveType(Type type) {
        return new SimpleTypeDescriptor<>(token.resolveType(type));
    }

    /**
     * Returns a set of {@link TypeDescriptor TypeDescriptor}, one for each
     * superclass as well as each interface implemented by this class.
     */
    @SuppressWarnings("rawtypes")
    public Iterable<TypeDescriptor> getTypes() {
        List<TypeDescriptor> interfaces = Lists.newArrayList();
        for (TypeToken<?> interfaceToken : token.getTypes()) {
            interfaces.add(new SimpleTypeDescriptor<>(interfaceToken));
        }
        return interfaces;
    }

    /**
     * Returns a set of {@link TypeDescriptor}s, one for each
     * interface implemented by this class.
     */
    @SuppressWarnings("rawtypes")
    public Iterable<TypeDescriptor> getInterfaces() {
        List<TypeDescriptor> interfaces = Lists.newArrayList();
        for (TypeToken<?> interfaceToken : token.getTypes().interfaces()) {
            interfaces.add(new SimpleTypeDescriptor<>(interfaceToken));
        }
        return interfaces;
    }

    /**
     * Returns a set of {@link TypeDescriptor}s, one for each
     * superclass (including this class).
     */
    @SuppressWarnings("rawtypes")
    public Iterable<TypeDescriptor> getClasses() {
        List<TypeDescriptor> classes = Lists.newArrayList();
        for (TypeToken<?> classToken : token.getTypes().classes()) {
            classes.add(new SimpleTypeDescriptor<>(classToken));
        }
        return classes;
    }

    /**
     * Returns a new {@code TypeDescriptor} where type variables represented by
     * {@code typeParameter} are substituted by {@code typeDescriptor}. For example, it can be used to
     * construct {@code Map<K, V>} for any {@code K} and {@code V} type: <pre> {@code
     *   static <K, V> TypeDescriptor<Map<K, V>> mapOf(
     *       TypeDescriptor<K> keyType, TypeDescriptor<V> valueType) {
     *     return new TypeDescriptor<Map<K, V>>() {}
     *         .where(new TypeParameter<K>() {}, keyType)
     *         .where(new TypeParameter<V>() {}, valueType);
     *   }}</pre>
     *
     * @param <X> The parameter type
     * @param typeParameter the parameter type variable
     * @param typeDescriptor the actual type to substitute
     */
    @SuppressWarnings("unchecked")
    public <X> TypeDescriptor<T> where(TypeParameter<X> typeParameter, TypeDescriptor<X> typeDescriptor) {
        TypeResolver resolver = new TypeResolver().where(typeParameter.typeVariable, typeDescriptor.getType());
        return (TypeDescriptor<T>) TypeDescriptor.of(resolver.resolveType(token.getType()));
    }

    @Override
    public String toString() {
        return token.toString();
    }

    /**
     * Two type descriptor are equal if and only if they
     * represent the same type.
     */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof TypeDescriptor)) {
            return false;
        } else {
            @SuppressWarnings("unchecked")
            TypeDescriptor<?> descriptor = (TypeDescriptor<?>) other;
            return token.equals(descriptor.token);
        }
    }

    @Override
    public int hashCode() {
        return token.hashCode();
    }

    /**
     * A non-abstract {@link TypeDescriptor} for construction directly from an existing
     * {@link TypeToken}.
     */
    private static final class SimpleTypeDescriptor<T> extends TypeDescriptor<T> {
        SimpleTypeDescriptor(TypeToken<T> typeToken) {
            super(typeToken);
        }
    }
}