/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.internal.engine.cascading;
import java.lang.reflect.AnnotatedArrayType;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
import javax.validation.valueextraction.ExtractedValue;
import javax.validation.valueextraction.UnwrapByDefault;
import javax.validation.valueextraction.ValueExtractor;
import org.hibernate.validator.internal.util.ReflectionHelper;
import org.hibernate.validator.internal.util.StringHelper;
import org.hibernate.validator.internal.util.TypeHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
/**
* Describes a {@link ValueExtractor}.
*
* @author Gunnar Morling
* @author Guillaume Smet
*/
public class ValueExtractorDescriptor {
private static final Log LOG = LoggerFactory.make();
private final Key key;
private final ValueExtractor<?> valueExtractor;
private final boolean unwrapByDefault;
public ValueExtractorDescriptor(ValueExtractor<?> valueExtractor) {
this.key = new Key(
getExtractedType( valueExtractor.getClass() ),
getExtractedTypeParameter( valueExtractor.getClass() )
);
this.valueExtractor = valueExtractor;
this.unwrapByDefault = hasUnwrapByDefaultAnnotation( valueExtractor.getClass() );
}
@SuppressWarnings("rawtypes")
private static TypeVariable<?> getExtractedTypeParameter(Class<? extends ValueExtractor> extractorImplementationType) {
AnnotatedParameterizedType valueExtractorDefinition = getValueExtractorDefinition( extractorImplementationType );
AnnotatedType extractedType = valueExtractorDefinition.getAnnotatedActualTypeArguments()[0];
Class<?> extractedTypeRaw = (Class<?>) TypeHelper.getErasedType( extractedType.getType() );
TypeVariable<?> extractedTypeParameter = null;
if ( extractedType.isAnnotationPresent( ExtractedValue.class ) ) {
if ( extractedType instanceof AnnotatedArrayType ) {
extractedTypeParameter = new ArrayElement( (AnnotatedArrayType) extractedType );
}
else {
extractedTypeParameter = AnnotatedObject.INSTANCE;
}
}
if ( extractedType instanceof AnnotatedParameterizedType ) {
AnnotatedParameterizedType parameterizedExtractedType = (AnnotatedParameterizedType) extractedType;
int i = 0;
for ( AnnotatedType typeArgument : parameterizedExtractedType.getAnnotatedActualTypeArguments() ) {
if ( typeArgument.isAnnotationPresent( ExtractedValue.class ) ) {
if ( extractedTypeParameter != null ) {
throw LOG.getValueExtractorDeclaresExtractedValueMultipleTimesException( extractorImplementationType );
}
extractedTypeParameter = extractedTypeRaw.getTypeParameters()[i];
}
i++;
}
}
if ( extractedTypeParameter == null ) {
throw LOG.getValueExtractorFailsToDeclareExtractedValueException( extractorImplementationType );
}
return extractedTypeParameter;
}
@SuppressWarnings("rawtypes")
private static Class<?> getExtractedType(Class<? extends ValueExtractor> extractorImplementationType) {
AnnotatedParameterizedType genericInterface = getValueExtractorDefinition( extractorImplementationType );
AnnotatedType extractedType = genericInterface.getAnnotatedActualTypeArguments()[0];
return TypeHelper.getErasedReferenceType( extractedType.getType() );
}
private static AnnotatedParameterizedType getValueExtractorDefinition(Class<?> extractorImplementationType) {
List<AnnotatedType> valueExtractorAnnotatedTypes = new ArrayList<>();
determineValueExtractorDefinitions( valueExtractorAnnotatedTypes, extractorImplementationType );
if ( valueExtractorAnnotatedTypes.size() == 1 ) {
return (AnnotatedParameterizedType) valueExtractorAnnotatedTypes.get( 0 );
}
else if ( valueExtractorAnnotatedTypes.size() > 1 ) {
throw LOG.getParallelDefinitionsOfValueExtractorException( extractorImplementationType );
}
else {
throw new AssertionError( extractorImplementationType.getName() + " should be a subclass of " + ValueExtractor.class.getSimpleName() );
}
}
private static void determineValueExtractorDefinitions(List<AnnotatedType> valueExtractorDefinitions, Class<?> extractorImplementationType) {
if ( !ValueExtractor.class.isAssignableFrom( extractorImplementationType ) ) {
return;
}
Class<?> superClass = extractorImplementationType.getSuperclass();
if ( superClass != null && !Object.class.equals( superClass ) ) {
determineValueExtractorDefinitions( valueExtractorDefinitions, superClass );
}
for ( Class<?> implementedInterface : extractorImplementationType.getInterfaces() ) {
if ( !ValueExtractor.class.equals( implementedInterface ) ) {
determineValueExtractorDefinitions( valueExtractorDefinitions, implementedInterface );
}
}
for ( AnnotatedType annotatedInterface : extractorImplementationType.getAnnotatedInterfaces() ) {
if ( ValueExtractor.class.equals( ReflectionHelper.getClassFromType( annotatedInterface.getType() ) ) ) {
valueExtractorDefinitions.add( annotatedInterface );
}
}
}
private static boolean hasUnwrapByDefaultAnnotation(Class<?> extractorImplementationType) {
return extractorImplementationType.isAnnotationPresent( UnwrapByDefault.class );
}
public Key getKey() {
return key;
}
public Class<?> getExtractedType() {
return key.extractedType;
}
public TypeVariable<?> getExtractedTypeParameter() {
return key.extractedTypeParameter;
}
public ValueExtractor<?> getValueExtractor() {
return valueExtractor;
}
public boolean isUnwrapByDefault() {
return unwrapByDefault;
}
@Override
public String toString() {
return "ValueExtractorDescriptor [key=" + key + ", valueExtractor=" + valueExtractor + ", unwrapByDefault=" + unwrapByDefault + "]";
}
public static class Key {
private final Class<?> extractedType;
private final TypeVariable<?> extractedTypeParameter;
private final int hashCode;
public Key(Class<?> extractedType, TypeVariable<?> extractedTypeParameter) {
this.extractedType = extractedType;
this.extractedTypeParameter = extractedTypeParameter;
this.hashCode = buildHashCode( extractedType, extractedTypeParameter );
}
private static int buildHashCode(Type extractedType, TypeVariable<?> extractedTypeParameter) {
final int prime = 31;
int result = 1;
result = prime * result + extractedType.hashCode();
result = prime * result + extractedTypeParameter.hashCode();
return result;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
Key other = (Key) obj;
return extractedType.equals( other.extractedType ) &&
extractedTypeParameter.equals( other.extractedTypeParameter );
}
@Override
public String toString() {
return "Key [extractedType=" + StringHelper.toShortString( extractedType ) + ", extractedTypeParameter=" + extractedTypeParameter + "]";
}
}
}