| /* |
| * 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 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; |
| import org.apache.beam.vendor.guava.v20_0.com.google.common.collect.Lists; |
| import org.apache.beam.vendor.guava.v20_0.com.google.common.reflect.Invokable; |
| import org.apache.beam.vendor.guava.v20_0.com.google.common.reflect.Parameter; |
| import org.apache.beam.vendor.guava.v20_0.com.google.common.reflect.TypeResolver; |
| import org.apache.beam.vendor.guava.v20_0.com.google.common.reflect.TypeToken; |
| |
| /** |
| * 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.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 the type variable represented by {@code |
| * typeParameter} are substituted by {@code type}. 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) { |
| return where(typeParameter.typeVariable, typeDescriptor.getType()); |
| } |
| |
| /** |
| * A more general form of {@link #where(TypeParameter, TypeDescriptor)} that returns a new {@code |
| * TypeDescriptor} by matching {@code formal} against {@code actual} to resolve type variables in |
| * the current {@link TypeDescriptor}. |
| */ |
| @SuppressWarnings("unchecked") |
| public TypeDescriptor<T> where(Type formal, Type actual) { |
| TypeResolver resolver = new TypeResolver().where(formal, actual); |
| return (TypeDescriptor<T>) TypeDescriptor.of(resolver.resolveType(token.getType())); |
| } |
| |
| /** |
| * Returns whether this {@link TypeDescriptor} has any unresolved type parameters, as opposed to |
| * being a concrete type. |
| * |
| * <p>For example: |
| * |
| * <pre>{@code |
| * TypeDescriptor.of(new ArrayList<String>() {}.getClass()).hasUnresolvedTypeParameters() |
| * => false, because the anonymous class is instantiated with a concrete type |
| * |
| * class TestUtils { |
| * <T> ArrayList<T> createTypeErasedList() { |
| * return new ArrayList<T>() {}; |
| * } |
| * } |
| * |
| * TypeDescriptor.of(TestUtils.<String>createTypeErasedList().getClass()) |
| * => true, because the type variable T got type-erased and the anonymous ArrayList class |
| * is instantiated with an unresolved type variable T. |
| * }</pre> |
| */ |
| public boolean hasUnresolvedParameters() { |
| return hasUnresolvedParameters(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); |
| } |
| } |
| } |