/* * 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); } } }