/* * 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 static org.hibernate.validator.internal.util.CollectionHelper.newHashMap; 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.stream.Collectors; import org.hibernate.validator.cfg.context.Cascadable; import org.hibernate.validator.cfg.context.ContainerElementConstraintMappingContext; import org.hibernate.validator.cfg.context.ContainerElementTarget; import org.hibernate.validator.cfg.context.GroupConversionTargetContext; 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.location.ConstraintLocation; import org.hibernate.validator.internal.util.Contracts; 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; /** * Base class for all implementations of cascadable context types. * * @author Gunnar Morling */ abstract class CascadableConstraintMappingContextImplBase<C extends Cascadable<C>> extends ConstraintMappingContextImplBase implements Cascadable<C> { private static final Log LOG = LoggerFactory.make(); private final Type configuredType; protected boolean isCascading; protected final Map<Class<?>, Class<?>> groupConversions = newHashMap(); /** * Contexts for configuring nested container elements, if any. Indexed by type parameter. */ private final Map<Integer, ContainerElementConstraintMappingContextImpl> containerElementContexts = new HashMap<>(); private final Set<ContainerElementPathKey> configuredPaths = new HashSet<>(); CascadableConstraintMappingContextImplBase(DefaultConstraintMapping mapping, Type configuredType) { super( mapping ); this.configuredType = configuredType; } /** * Returns this object, narrowed down to the specific sub-type. * * @return this object, narrowed down to the specific sub-type * * @see <a href="http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ206">"Get this" trick</a> */ protected abstract C getThis(); /** * Adds a group conversion for this element. * * @param from the source group of the conversion * @param to the target group of the conversion */ public void addGroupConversion(Class<?> from, Class<?> to) { groupConversions.put( from, to ); } @Override public C valid() { isCascading = true; return getThis(); } @Override public GroupConversionTargetContext<C> convertGroup(Class<?> from) { return new GroupConversionTargetContextImpl<>( from, getThis(), this ); } public ContainerElementConstraintMappingContext containerElement(ContainerElementTarget parent, TypeConstraintMappingContextImpl<?> typeContext, ConstraintLocation location) { if ( configuredType instanceof ParameterizedType ) { if ( ( (ParameterizedType) configuredType ).getActualTypeArguments().length > 1 ) { throw LOG.getNoTypeArgumentIndexIsGivenForTypeWithMultipleTypeArgumentsException( configuredType ); } } else if ( !TypeHelper.isArray( configuredType ) ) { throw LOG.getTypeIsNotAParameterizedNorArrayTypeException( configuredType ); } return containerElement( parent, typeContext, location, 0 ); } public ContainerElementConstraintMappingContext containerElement(ContainerElementTarget parent, TypeConstraintMappingContextImpl<?> typeContext, ConstraintLocation location, int index, int... nestedIndexes) { Contracts.assertTrue( index >= 0, "Type argument index must not be negative" ); if ( !( configuredType instanceof ParameterizedType ) && !( TypeHelper.isArray( configuredType ) ) ) { throw LOG.getTypeIsNotAParameterizedNorArrayTypeException( configuredType ); } ContainerElementPathKey key = new ContainerElementPathKey( index, nestedIndexes ); boolean configuredBefore = !configuredPaths.add( key ); if ( configuredBefore ) { throw LOG.getContainerElementTypeHasAlreadyBeenConfiguredViaProgrammaticApiException( location.getTypeForValidatorResolution() ); } ContainerElementConstraintMappingContextImpl containerElementContext = new ContainerElementConstraintMappingContextImpl( typeContext, parent, location, index ); containerElementContexts.put( index, containerElementContext ); if ( nestedIndexes.length > 0 ) { return containerElementContext.nestedContainerElement( nestedIndexes ); } else { return containerElementContext; } } public boolean isCascading() { return isCascading; } protected Set<MetaConstraint<?>> getTypeArgumentConstraints(ConstraintHelper constraintHelper, TypeResolutionHelper typeResolutionHelper, ValueExtractorManager valueExtractorManager) { return containerElementContexts.values() .stream() .map( t -> t.build( constraintHelper, typeResolutionHelper, valueExtractorManager ) ) .flatMap( Set::stream ) .collect( Collectors.toSet() ); } protected CascadingTypeParameter getCascadingMetaData() { Map<TypeVariable<?>, CascadingTypeParameter> typeParametersCascadingMetaData = containerElementContexts.values().stream() .filter( c -> c.getCascadingTypeParameter() != null ) .collect( Collectors.toMap( c -> c.getCascadingTypeParameter().getTypeParameter(), c -> c.getCascadingTypeParameter() ) ); for ( ContainerElementConstraintMappingContextImpl typeArgumentContext : containerElementContexts.values() ) { CascadingTypeParameter cascadingTypeParameter = typeArgumentContext.getCascadingTypeParameter(); if ( cascadingTypeParameter != null ) { typeParametersCascadingMetaData.put( cascadingTypeParameter.getTypeParameter(), cascadingTypeParameter ); } } boolean isArray = TypeHelper.isArray( configuredType ); CascadingTypeParameter cascadingMetaData = isArray ? CascadingTypeParameter.arrayElement( configuredType, isCascading, typeParametersCascadingMetaData, groupConversions ) : CascadingTypeParameter.annotatedObject( configuredType, isCascading, typeParametersCascadingMetaData, groupConversions ); return cascadingMetaData; } private static class ContainerElementPathKey { private final int index; private final int[] nestedIndexes; public ContainerElementPathKey(int index, int[] nestedIndexes) { this.index = index; this.nestedIndexes = nestedIndexes; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + index; result = prime * result + Arrays.hashCode( nestedIndexes ); return result; } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } ContainerElementPathKey other = (ContainerElementPathKey) obj; if ( index != other.index ) { return false; } if ( !Arrays.equals( nestedIndexes, other.nestedIndexes ) ) { return false; } return true; } } }