// code originates from TypeUtils.java in jtype (http://code.google.com/p/jtype/) and has been modified to suite
// the HV requirements and code style
/*
 * Copyright 2009 IIZUKA Software Technologies Ltd
 *
 * Licensed 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.hibernate.validator.internal.util;

import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.MalformedParameterizedTypeException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.validation.ConstraintValidator;

import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorDescriptor;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;

Provides utility methods for working with types.
Author:Mark Hobson, Hardy Ferentschik
/** * Provides utility methods for working with types. * * @author Mark Hobson * @author Hardy Ferentschik */
public final class TypeHelper { private static final Map<Class<?>, Set<Class<?>>> SUBTYPES_BY_PRIMITIVE; private static final int CONSTRAINT_TYPE_INDEX = 0; private static final int VALIDATOR_TYPE_INDEX = 1; private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); static { Map<Class<?>, Set<Class<?>>> subtypesByPrimitive = newHashMap(); putPrimitiveSubtypes( subtypesByPrimitive, Void.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Boolean.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Byte.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Character.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Short.TYPE, Byte.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Integer.TYPE, Character.TYPE, Short.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Long.TYPE, Integer.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Float.TYPE, Long.TYPE ); putPrimitiveSubtypes( subtypesByPrimitive, Double.TYPE, Float.TYPE ); SUBTYPES_BY_PRIMITIVE = Collections.unmodifiableMap( subtypesByPrimitive ); } private TypeHelper() { throw new AssertionError(); } public static boolean isAssignable(Type supertype, Type type) { Contracts.assertNotNull( supertype, "supertype" ); Contracts.assertNotNull( type, "type" ); if ( supertype.equals( type ) ) { return true; } if ( supertype instanceof Class<?> ) { if ( type instanceof Class<?> ) { return isClassAssignable( (Class<?>) supertype, (Class<?>) type ); } if ( type instanceof ParameterizedType ) { return isAssignable( supertype, ( (ParameterizedType) type ).getRawType() ); } if ( type instanceof TypeVariable<?> ) { return isTypeVariableAssignable( supertype, (TypeVariable<?>) type ); } if ( type instanceof GenericArrayType ) { if ( ( (Class<?>) supertype ).isArray() ) { return isAssignable( getComponentType( supertype ), getComponentType( type ) ); } return isArraySupertype( (Class<?>) supertype ); } if ( type instanceof WildcardType ) { return isClassAssignableToWildcardType( (Class<?>) supertype, (WildcardType) type ); } return false; } if ( supertype instanceof ParameterizedType ) { if ( type instanceof Class<?> ) { return isSuperAssignable( supertype, type ); } if ( type instanceof ParameterizedType ) { return isParameterizedTypeAssignable( (ParameterizedType) supertype, (ParameterizedType) type ); } return false; } if ( type instanceof TypeVariable<?> ) { return isTypeVariableAssignable( supertype, (TypeVariable<?>) type ); } if ( supertype instanceof GenericArrayType ) { if ( isArray( type ) ) { return isAssignable( getComponentType( supertype ), getComponentType( type ) ); } return false; } if ( supertype instanceof WildcardType ) { return isWildcardTypeAssignable( (WildcardType) supertype, type ); } return false; }
Gets the erased type of the specified type.
Params:
  • type – the type to perform erasure on
See Also:
Returns:the erased type, never a parameterized type nor a type variable
/** * Gets the erased type of the specified type. * * @param type the type to perform erasure on * * @return the erased type, never a parameterized type nor a type variable * * @see <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6">4.6 Type Erasure</a> */
public static Type getErasedType(Type type) { // the erasure of a parameterized type G<T1, ... ,Tn> is |G| if ( type instanceof ParameterizedType ) { Type rawType = ( (ParameterizedType) type ).getRawType(); return getErasedType( rawType ); } // TODO: the erasure of a nested type T.C is |T|.C // the erasure of an array type T[] is |T|[] if ( isArray( type ) ) { Type componentType = getComponentType( type ); Type erasedComponentType = getErasedType( componentType ); return getArrayType( erasedComponentType ); } // the erasure of a type variable is the erasure of its leftmost bound if ( type instanceof TypeVariable<?> ) { Type[] bounds = ( (TypeVariable<?>) type ).getBounds(); return getErasedType( bounds[0] ); } // the erasure of a wildcard type is the erasure of its leftmost upper bound if ( type instanceof WildcardType ) { Type[] upperBounds = ( (WildcardType) type ).getUpperBounds(); return getErasedType( upperBounds[0] ); } // the erasure of every other type is the type itself return type; } public static Class<?> getErasedReferenceType(Type type) { Contracts.assertTrue( isReferenceType( type ), "type is not a reference type: %s", type ); return (Class<?>) getErasedType( type ); } public static boolean isArray(Type type) { return ( type instanceof Class<?> && ( (Class<?>) type ).isArray() ) || ( type instanceof GenericArrayType ); } public static Type getComponentType(Type type) { if ( type instanceof Class<?> ) { Class<?> klass = (Class<?>) type; return klass.isArray() ? klass.getComponentType() : null; } if ( type instanceof GenericArrayType ) { return ( (GenericArrayType) type ).getGenericComponentType(); } return null; } private static Type getArrayType(Type componentType) { Contracts.assertNotNull( componentType, "componentType" ); if ( componentType instanceof Class<?> ) { return Array.newInstance( (Class<?>) componentType, 0 ).getClass(); } return genericArrayType( componentType ); }
Creates a generic array type for the specified component type.
Params:
  • componentType – the component type
Returns:the generic array type
/** * Creates a generic array type for the specified component type. * * @param componentType the component type * * @return the generic array type */
public static GenericArrayType genericArrayType(final Type componentType) { return new GenericArrayType() { @Override public Type getGenericComponentType() { return componentType; } }; } public static boolean isInstance(Type type, Object object) { return getErasedReferenceType( type ).isInstance( object ); }
Creates a parameterized type for the specified raw type and actual type arguments.
Params:
  • rawType – the raw type
  • actualTypeArguments – the actual type arguments
Throws:
Returns:the parameterized type
/** * Creates a parameterized type for the specified raw type and actual type arguments. * * @param rawType the raw type * @param actualTypeArguments the actual type arguments * * @return the parameterized type * * @throws MalformedParameterizedTypeException if the number of actual type arguments differs from those defined on the raw type */
public static ParameterizedType parameterizedType(final Class<?> rawType, final Type... actualTypeArguments) { return new ParameterizedType() { @Override public Type[] getActualTypeArguments() { return actualTypeArguments; } @Override public Type getRawType() { return rawType; } @Override public Type getOwnerType() { return null; } }; } private static Type getResolvedSuperclass(Type type) { Contracts.assertNotNull( type, "type" ); Class<?> rawType = getErasedReferenceType( type ); Type supertype = rawType.getGenericSuperclass(); if ( supertype == null ) { return null; } return resolveTypeVariables( supertype, type ); } private static Type[] getResolvedInterfaces(Type type) { Contracts.assertNotNull( type, "type" ); Class<?> rawType = getErasedReferenceType( type ); Type[] interfaces = rawType.getGenericInterfaces(); Type[] resolvedInterfaces = new Type[interfaces.length]; for ( int i = 0; i < interfaces.length; i++ ) { resolvedInterfaces[i] = resolveTypeVariables( interfaces[i], type ); } return resolvedInterfaces; }
Params:
  • annotationType – The annotation type.
  • validators – List of constraint validator classes (for a given constraint).
Type parameters:
  • <A> – the type of the annotation
Returns:Return a Map<Class, Class<? extends ConstraintValidator>> where the map key is the type the validator accepts and value the validator class itself.
/** * @param annotationType The annotation type. * @param validators List of constraint validator classes (for a given constraint). * @param <A> the type of the annotation * * @return Return a Map&lt;Class, Class&lt;? extends ConstraintValidator&gt;&gt; where the map * key is the type the validator accepts and value the validator class itself. */
public static <A extends Annotation> Map<Type, ConstraintValidatorDescriptor<A>> getValidatorTypes( Class<A> annotationType, List<ConstraintValidatorDescriptor<A>> validators) { Map<Type, ConstraintValidatorDescriptor<A>> validatorsTypes = newHashMap(); for ( ConstraintValidatorDescriptor<A> validator : validators ) { Type type = validator.getValidatedType(); ConstraintValidatorDescriptor<A> previous = validatorsTypes.put( type, validator ); if ( previous != null ) { throw LOG.getMultipleValidatorsForSameTypeException( annotationType, type, previous.getValidatorClass(), validator.getValidatorClass() ); } } return validatorsTypes; } public static Type extractValidatedType(Class<? extends ConstraintValidator<?, ?>> validator) { return extractConstraintValidatorTypeArgumentType( validator, VALIDATOR_TYPE_INDEX ); } public static Type extractConstraintType(Class<? extends ConstraintValidator<?, ?>> validator) { return extractConstraintValidatorTypeArgumentType( validator, CONSTRAINT_TYPE_INDEX ); } public static Type extractConstraintValidatorTypeArgumentType(Class<? extends ConstraintValidator<?, ?>> validator, int typeArgumentIndex) { Map<Type, Type> resolvedTypes = new HashMap<>(); Type constraintValidatorType = resolveTypes( resolvedTypes, validator ); //we now have all bind from a type to its resolution at one level Type type = ( (ParameterizedType) constraintValidatorType ).getActualTypeArguments()[typeArgumentIndex]; if ( type == null ) { throw LOG.getNullIsAnInvalidTypeForAConstraintValidatorException(); } else if ( type instanceof GenericArrayType ) { type = TypeHelper.getArrayType( TypeHelper.getComponentType( type ) ); } while ( resolvedTypes.containsKey( type ) ) { type = resolvedTypes.get( type ); } //FIXME raise an exception if validatorType is not a class return type; } public static boolean isUnboundWildcard(Type type) { if ( !( type instanceof WildcardType ) ) { return false; } WildcardType wildcardType = (WildcardType) type; return isEmptyBounds( wildcardType.getUpperBounds() ) && isEmptyBounds( wildcardType.getLowerBounds() ); } private static Type resolveTypes(Map<Type, Type> resolvedTypes, Type type) { if ( type == null ) { return null; } else if ( type instanceof Class ) { Class<?> clazz = (Class<?>) type; final Type returnedType = resolveTypeForClassAndHierarchy( resolvedTypes, clazz ); if ( returnedType != null ) { return returnedType; } } else if ( type instanceof ParameterizedType ) { ParameterizedType paramType = (ParameterizedType) type; if ( !( paramType.getRawType() instanceof Class ) ) { return null; //don't know what to do here } Class<?> rawType = (Class<?>) paramType.getRawType(); TypeVariable<?>[] originalTypes = rawType.getTypeParameters(); Type[] partiallyResolvedTypes = paramType.getActualTypeArguments(); int nbrOfParams = originalTypes.length; for ( int i = 0; i < nbrOfParams; i++ ) { resolvedTypes.put( originalTypes[i], partiallyResolvedTypes[i] ); } if ( rawType.equals( ConstraintValidator.class ) ) { //we found our baby return type; } else { Type returnedType = resolveTypeForClassAndHierarchy( resolvedTypes, rawType ); if ( returnedType != null ) { return returnedType; } } } //else we don't care I think return null; } private static Type resolveTypeForClassAndHierarchy(Map<Type, Type> resolvedTypes, Class<?> clazz) { Type returnedType = resolveTypes( resolvedTypes, clazz.getGenericSuperclass() ); if ( returnedType != null ) { return returnedType; } for ( Type genericInterface : clazz.getGenericInterfaces() ) { returnedType = resolveTypes( resolvedTypes, genericInterface ); if ( returnedType != null ) { return returnedType; } } return null; } private static void putPrimitiveSubtypes(Map<Class<?>, Set<Class<?>>> subtypesByPrimitive, Class<?> primitiveType, Class<?>... directSubtypes) { Set<Class<?>> subtypes = newHashSet(); for ( Class<?> directSubtype : directSubtypes ) { subtypes.add( directSubtype ); subtypes.addAll( subtypesByPrimitive.get( directSubtype ) ); } subtypesByPrimitive.put( primitiveType, Collections.unmodifiableSet( subtypes ) ); } private static boolean isClassAssignable(Class<?> supertype, Class<?> type) { // Class.isAssignableFrom does not perform primitive widening if ( supertype.isPrimitive() && type.isPrimitive() ) { return SUBTYPES_BY_PRIMITIVE.get( supertype ).contains( type ); } return supertype.isAssignableFrom( type ); } private static boolean isClassAssignableToWildcardType(Class<?> supertype, WildcardType type) { for ( Type upperBound : type.getUpperBounds() ) { if ( !isAssignable( supertype, upperBound ) ) { return false; } } return true; } private static boolean isParameterizedTypeAssignable(ParameterizedType supertype, ParameterizedType type) { Type rawSupertype = supertype.getRawType(); Type rawType = type.getRawType(); if ( !rawSupertype.equals( rawType ) ) { // short circuit when class raw types are unassignable if ( rawSupertype instanceof Class<?> && rawType instanceof Class<?> && !( ( (Class<?>) rawSupertype ).isAssignableFrom( (Class<?>) rawType ) ) ) { return false; } return isSuperAssignable( supertype, type ); } Type[] supertypeArgs = supertype.getActualTypeArguments(); Type[] typeArgs = type.getActualTypeArguments(); if ( supertypeArgs.length != typeArgs.length ) { return false; } for ( int i = 0; i < supertypeArgs.length; i++ ) { Type supertypeArg = supertypeArgs[i]; Type typeArg = typeArgs[i]; if ( supertypeArg instanceof WildcardType ) { if ( !isWildcardTypeAssignable( (WildcardType) supertypeArg, typeArg ) ) { return false; } } else if ( !supertypeArg.equals( typeArg ) ) { return false; } } return true; } private static boolean isTypeVariableAssignable(Type supertype, TypeVariable<?> type) { for ( Type bound : type.getBounds() ) { if ( isAssignable( supertype, bound ) ) { return true; } } return false; } private static boolean isWildcardTypeAssignable(WildcardType supertype, Type type) { for ( Type upperBound : supertype.getUpperBounds() ) { if ( !isAssignable( upperBound, type ) ) { return false; } } for ( Type lowerBound : supertype.getLowerBounds() ) { if ( !isAssignable( type, lowerBound ) ) { return false; } } return true; } private static boolean isSuperAssignable(Type supertype, Type type) { Type superclass = getResolvedSuperclass( type ); if ( superclass != null && isAssignable( supertype, superclass ) ) { return true; } for ( Type interphace : getResolvedInterfaces( type ) ) { if ( isAssignable( supertype, interphace ) ) { return true; } } return false; }
Gets whether the specified type is a reference type.

More specifically, this method returns true if the specified type is one of the following:

  • a class type
  • an interface type
  • an array type
  • a parameterized type
  • a type variable
  • the null type
Params:
  • type – the type to check
See Also:
Returns:true if the specified type is a reference type
/** * Gets whether the specified type is a <em>reference type</em>. * <p> * More specifically, this method returns {@code true} if the specified type is one of the following: * <ul> * <li>a class type</li> * <li>an interface type</li> * <li>an array type</li> * <li>a parameterized type</li> * <li>a type variable</li> * <li>the null type</li> * </ul> * * @param type the type to check * * @return {@code true} if the specified type is a reference type * * @see <a href="http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.3">4.3 Reference Types and Values</a> */
private static boolean isReferenceType(Type type) { return type == null || type instanceof Class<?> || type instanceof ParameterizedType || type instanceof TypeVariable<?> || type instanceof GenericArrayType; } private static boolean isArraySupertype(Class<?> type) { return Object.class.equals( type ) || Cloneable.class.equals( type ) || Serializable.class.equals( type ); } private static Type resolveTypeVariables(Type type, Type subtype) { // TODO: need to support other types in future, e.g. T[], etc. if ( !( type instanceof ParameterizedType ) ) { return type; } Map<Type, Type> actualTypeArgumentsByParameter = getActualTypeArgumentsByParameter( type, subtype ); Class<?> rawType = getErasedReferenceType( type ); return parameterizeClass( rawType, actualTypeArgumentsByParameter ); } private static Map<Type, Type> getActualTypeArgumentsByParameter(Type... types) { // TODO: return Map<TypeVariable<Class<?>>, Type> somehow Map<Type, Type> actualTypeArgumentsByParameter = new LinkedHashMap<>(); for ( Type type : types ) { actualTypeArgumentsByParameter.putAll( getActualTypeArgumentsByParameterInternal( type ) ); } return normalize( actualTypeArgumentsByParameter ); } private static Map<Type, Type> getActualTypeArgumentsByParameterInternal(Type type) { // TODO: look deeply within non-parameterized types when visitors implemented if ( !( type instanceof ParameterizedType ) ) { return Collections.emptyMap(); } TypeVariable<?>[] typeParameters = getErasedReferenceType( type ).getTypeParameters(); Type[] typeArguments = ( (ParameterizedType) type ).getActualTypeArguments(); if ( typeParameters.length != typeArguments.length ) { throw new MalformedParameterizedTypeException(); } Map<Type, Type> actualTypeArgumentsByParameter = new LinkedHashMap<>(); for ( int i = 0; i < typeParameters.length; i++ ) { // we only add the mapping if it is not a cyclic dependency (see HV-1032) if ( !typeParameters[i].equals( typeArguments[i] ) ) { actualTypeArgumentsByParameter.put( typeParameters[i], typeArguments[i] ); } } return actualTypeArgumentsByParameter; } private static ParameterizedType parameterizeClass(Class<?> type, Map<Type, Type> actualTypeArgumentsByParameter) { return parameterizeClassCapture( type, actualTypeArgumentsByParameter ); } private static <T> ParameterizedType parameterizeClassCapture(Class<T> type, Map<Type, Type> actualTypeArgumentsByParameter) { // TODO: actualTypeArgumentsByParameter should be Map<TypeVariable<Class<T>>, Type> TypeVariable<Class<T>>[] typeParameters = type.getTypeParameters(); Type[] actualTypeArguments = new Type[typeParameters.length]; for ( int i = 0; i < typeParameters.length; i++ ) { TypeVariable<Class<T>> typeParameter = typeParameters[i]; Type actualTypeArgument = actualTypeArgumentsByParameter.get( typeParameter ); if ( actualTypeArgument == null ) { throw LOG.getMissingActualTypeArgumentForTypeParameterException( typeParameter ); } actualTypeArguments[i] = actualTypeArgument; } return parameterizedType( getErasedReferenceType( type ), actualTypeArguments ); } private static <K, V> Map<K, V> normalize(Map<K, V> map) { // TODO: will this cause an infinite loop with recursive bounds? for ( Entry<K, V> entry : map.entrySet() ) { K key = entry.getKey(); V value = entry.getValue(); while ( map.containsKey( value ) ) { value = map.get( value ); } map.put( key, value ); } return map; } private static boolean isEmptyBounds(Type[] bounds) { return bounds == null || bounds.length == 0 || ( bounds.length == 1 && Object.class.equals( bounds[0] ) ); } }