org.hibernate.validator.internal.util.TypeHelper Maven / Gradle / Ivy
// 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 jakarta.validation.ConstraintValidator;
import org.hibernate.validator.engine.HibernateValidatorEnhancedBean;
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
* @author Hardy Ferentschik
*/
public final class TypeHelper {
private static final Map, Set>> 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, Set>> 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 isHibernateValidatorEnhancedBean(Class clazz) {
return HibernateValidatorEnhancedBean.class.isAssignableFrom( clazz );
}
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.
*
* @param type the type to perform erasure on
*
* @return the erased type, never a parameterized type nor a type variable
*
* @see 4.6 Type Erasure
*/
public static Type getErasedType(Type type) {
// the erasure of a parameterized type G 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.
*
* @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.
*
* @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;
}
/**
* @param annotationType The annotation type.
* @param validators List of constraint validator classes (for a given constraint).
* @param the type of the annotation
*
* @return Return a Map<Class, Class<? extends ConstraintValidator>> where the map
* key is the type the validator accepts and value the validator class itself.
*/
public static Map> getValidatorTypes(
Class annotationType,
List> validators) {
Map> validatorsTypes = newHashMap();
for ( ConstraintValidatorDescriptor validator : validators ) {
Type type = validator.getValidatedType();
ConstraintValidatorDescriptor previous = validatorsTypes.put( type, validator );
if ( previous != null ) {
throw LOG.getMultipleValidatorsForSameTypeException( annotationType, type, previous.getValidatorClass(), validator.getValidatorClass() );
}
}
return validatorsTypes;
}
public static Type extractValidatedType(Class> validator) {
return extractConstraintValidatorTypeArgumentType( validator, VALIDATOR_TYPE_INDEX );
}
public static Type extractConstraintType(Class> validator) {
return extractConstraintValidatorTypeArgumentType( validator, CONSTRAINT_TYPE_INDEX );
}
public static Type extractConstraintValidatorTypeArgumentType(Class> validator, int typeArgumentIndex) {
Map 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 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 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, Set>> subtypesByPrimitive, Class primitiveType,
Class... directSubtypes) {
Set> 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 {@code 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
* - a wildcard type
*
*
* @param type the type to check
*
* @return {@code true} if the specified type is a reference type
*
* @see 4.3 Reference Types and Values
*/
private static boolean isReferenceType(Type type) {
return type == null
|| type instanceof Class
|| type instanceof ParameterizedType
|| type instanceof TypeVariable
|| type instanceof GenericArrayType
|| type instanceof WildcardType;
}
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 actualTypeArgumentsByParameter = getActualTypeArgumentsByParameter( type, subtype );
Class rawType = getErasedReferenceType( type );
return parameterizeClass( rawType, actualTypeArgumentsByParameter );
}
private static Map getActualTypeArgumentsByParameter(Type... types) {
// TODO: return Map>, Type> somehow
Map actualTypeArgumentsByParameter = new LinkedHashMap<>();
for ( Type type : types ) {
actualTypeArgumentsByParameter.putAll( getActualTypeArgumentsByParameterInternal( type ) );
}
return normalize( actualTypeArgumentsByParameter );
}
private static Map 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 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 actualTypeArgumentsByParameter) {
return parameterizeClassCapture( type, actualTypeArgumentsByParameter );
}
private static ParameterizedType parameterizeClassCapture(Class type, Map actualTypeArgumentsByParameter) {
// TODO: actualTypeArgumentsByParameter should be Map>, Type>
TypeVariable>[] typeParameters = type.getTypeParameters();
Type[] actualTypeArguments = new Type[typeParameters.length];
for ( int i = 0; i < typeParameters.length; i++ ) {
TypeVariable> 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 Map normalize(Map map) {
// TODO: will this cause an infinite loop with recursive bounds?
for ( Entry 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] ) );
}
}