blob: ceb673a33c22ade552eecb23e1d50ee5214446ee [file] [log] [blame]
/*
* 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);
}
}
}