/*
* 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.cfg.context;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hibernate.validator.cfg.ConstraintDef;
import org.hibernate.validator.cfg.context.ConstructorConstraintMappingContext;
import org.hibernate.validator.cfg.context.ContainerElementConstraintMappingContext;
import org.hibernate.validator.cfg.context.ContainerElementTarget;
import org.hibernate.validator.cfg.context.MethodConstraintMappingContext;
import org.hibernate.validator.cfg.context.ParameterConstraintMappingContext;
import org.hibernate.validator.cfg.context.ParameterTarget;
import org.hibernate.validator.cfg.context.PropertyConstraintMappingContext;
import org.hibernate.validator.cfg.context.ReturnValueConstraintMappingContext;
import org.hibernate.validator.cfg.context.ReturnValueTarget;
import org.hibernate.validator.internal.engine.cascading.ArrayElement;
import org.hibernate.validator.internal.engine.cascading.ValueExtractorManager;
import org.hibernate.validator.internal.metadata.cascading.CascadingTypeParameter;
import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
import org.hibernate.validator.internal.metadata.core.MetaConstraint;
import org.hibernate.validator.internal.metadata.core.MetaConstraints;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.ConstraintType;
import org.hibernate.validator.internal.metadata.location.ConstraintLocation;
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.TypeResolutionHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
/**
* @author Gunnar Morling
*
*/
public class ContainerElementConstraintMappingContextImpl extends CascadableConstraintMappingContextImplBase<ContainerElementConstraintMappingContext> implements ContainerElementConstraintMappingContext {
private static final Log LOG = LoggerFactory.make();
private final TypeConstraintMappingContextImpl<?> typeContext;
private final ContainerElementTarget parentContainerElementTarget;
private final ConstraintLocation parentLocation;
/**
* The type configured through this context. Either a {@code ParameterizedType} or an array type.
*/
private final Type configuredType;
/**
* The index of the type parameter configured through this context. Always 0 in case of an array type.
*/
private final int index;
/**
* The type parameter configured through this context. An instance of {@link ArrayElement} in case of an array type.
*/
private final TypeVariable<?> typeParameter;
/**
* Contexts for configuring nested container elements, if any. Indexed by type parameter.
*/
protected final Map<Integer, ContainerElementConstraintMappingContextImpl> nestedContainerElementContexts;
private final Set<ConfiguredConstraint<?>> constraints;
ContainerElementConstraintMappingContextImpl(TypeConstraintMappingContextImpl<?> typeContext, ContainerElementTarget parentContainerElementTarget,
ConstraintLocation parentLocation, int index) {
super( typeContext.getConstraintMapping(), parentLocation.getTypeForValidatorResolution() );
this.typeContext = typeContext;
this.parentContainerElementTarget = parentContainerElementTarget;
this.parentLocation = parentLocation;
this.configuredType = parentLocation.getTypeForValidatorResolution();
if ( parentLocation.getTypeForValidatorResolution() instanceof ParameterizedType ) {
TypeVariable<?>[] typeParameters = ReflectionHelper.getClassFromType( configuredType ).getTypeParameters();
if ( index > typeParameters.length - 1 ) {
throw LOG.getInvalidTypeArgumentIndexException( configuredType, index );
}
else {
this.typeParameter = typeParameters[index];
}
}
else {
typeParameter = new ArrayElement( configuredType );
}
this.index = index;
this.constraints = new HashSet<>();
this.nestedContainerElementContexts = new HashMap<>();
}
@Override
protected ContainerElementConstraintMappingContext getThis() {
return this;
}
@Override
public PropertyConstraintMappingContext property(String property, ElementType elementType) {
return typeContext.property( property, elementType );
}
@Override
public ConstructorConstraintMappingContext constructor(Class<?>... parameterTypes) {
return typeContext.constructor( parameterTypes );
}
@Override
public MethodConstraintMappingContext method(String name, Class<?>... parameterTypes) {
return typeContext.method( name, parameterTypes );
}
@Override
public ParameterConstraintMappingContext parameter(int index) {
if ( parentContainerElementTarget instanceof ParameterTarget ) {
return ( (ParameterTarget) parentContainerElementTarget ).parameter( index );
}
else {
throw LOG.getParameterIsNotAValidCallException();
}
}
@Override
public ReturnValueConstraintMappingContext returnValue() {
if ( parentContainerElementTarget instanceof ReturnValueTarget ) {
return ( (ReturnValueTarget) parentContainerElementTarget ).returnValue();
}
else {
throw LOG.getReturnValueIsNotAValidCallException();
}
}
@Override
public ContainerElementConstraintMappingContext containerElementType() {
return parentContainerElementTarget.containerElementType( 0 );
}
@Override
public ContainerElementConstraintMappingContext containerElementType(int index, int... nestedIndexes) {
return parentContainerElementTarget.containerElementType( index, nestedIndexes );
}
ContainerElementConstraintMappingContext nestedContainerElement(int[] nestedIndexes) {
if ( !( configuredType instanceof ParameterizedType ) && !( TypeHelper.isArray( configuredType ) ) ) {
throw LOG.getTypeIsNotAParameterizedNorArrayTypeException( configuredType );
}
ContainerElementConstraintMappingContextImpl nestedContext = new ContainerElementConstraintMappingContextImpl(
typeContext,
parentContainerElementTarget,
ConstraintLocation.forTypeArgument( parentLocation, typeParameter, getContainerElementType() ),
nestedIndexes[0]
);
nestedContainerElementContexts.put( nestedIndexes[0], nestedContext );
if ( nestedIndexes.length > 1 ) {
return nestedContext.nestedContainerElement( Arrays.copyOfRange( nestedIndexes, 1, nestedIndexes.length ) );
}
else {
return nestedContext;
}
}
@Override
public ContainerElementConstraintMappingContext constraint(ConstraintDef<?, ?> definition) {
constraints.add( ConfiguredConstraint.forTypeArgument( definition, parentLocation, typeParameter, getContainerElementType() ) );
return this;
}
private Type getContainerElementType() {
if ( configuredType instanceof ParameterizedType ) {
return ( (ParameterizedType) configuredType ).getActualTypeArguments()[index];
}
else {
return TypeHelper.getComponentType( configuredType );
}
}
@Override
protected ConstraintType getConstraintType() {
return ConstraintType.GENERIC;
}
CascadingTypeParameter getCascadingTypeParameter() {
return new CascadingTypeParameter(
parentLocation.getTypeForValidatorResolution(),
typeParameter,
isCascading,
nestedContainerElementContexts.values()
.stream()
.map( ContainerElementConstraintMappingContextImpl::getCascadingTypeParameter )
.collect( Collectors.toMap( CascadingTypeParameter::getTypeParameter, Function.identity() ) ),
groupConversions
);
}
Set<MetaConstraint<?>> build(ConstraintHelper constraintHelper, TypeResolutionHelper typeResolutionHelper,
ValueExtractorManager valueExtractorManager) {
return Stream.concat(
constraints.stream()
.map( c -> asMetaConstraint( c, constraintHelper, typeResolutionHelper, valueExtractorManager ) ),
nestedContainerElementContexts.values()
.stream()
.map( c -> c.build( constraintHelper, typeResolutionHelper, valueExtractorManager ) )
.flatMap( Set::stream )
)
.collect( Collectors.toSet() );
}
private <A extends Annotation> MetaConstraint<A> asMetaConstraint(ConfiguredConstraint<A> config, ConstraintHelper constraintHelper,
TypeResolutionHelper typeResolutionHelper, ValueExtractorManager valueExtractorManager) {
ConstraintDescriptorImpl<A> constraintDescriptor = new ConstraintDescriptorImpl<>(
constraintHelper,
config.getLocation().getMember(),
config.createAnnotationProxy(),
config.getElementType(),
getConstraintType()
);
return MetaConstraints.create( typeResolutionHelper, valueExtractorManager, constraintDescriptor, config.getLocation() );
}
@Override
public String toString() {
return "TypeArgumentConstraintMappingContextImpl [configuredType=" + StringHelper.toShortString( configuredType ) + ", typeParameter=" + typeParameter
+ "]";
}
}