/* * 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.metadata.aggregated; import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.validation.ElementKind; import javax.validation.metadata.ContainerElementTypeDescriptor; import javax.validation.metadata.GroupConversionDescriptor; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; import org.hibernate.validator.internal.metadata.descriptor.ContainerElementTypeDescriptorImpl; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; import org.hibernate.validator.internal.metadata.location.TypeArgumentConstraintLocation; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.ReflectionHelper; import org.hibernate.validator.internal.util.TypeVariables; import org.hibernate.validator.internal.util.stereotypes.Immutable; /** * Base implementation for {@link ConstraintMetaData} with attributes common * to all type of meta data. * * @author Gunnar Morling * @author Hardy Ferentschik */ public abstract class AbstractConstraintMetaData implements ConstraintMetaData { private final String name; private final Type type; private final ElementKind constrainedMetaDataKind; @Immutable private final Set<MetaConstraint<?>> directConstraints; @Immutable private final Set<MetaConstraint<?>> containerElementsConstraints; @Immutable private final Set<MetaConstraint<?>> allConstraints; private final boolean isCascading; private final boolean isConstrained; public AbstractConstraintMetaData(String name, Type type, Set<MetaConstraint<?>> directConstraints, Set<MetaConstraint<?>> containerElementsConstraints, ElementKind constrainedMetaDataKind, boolean isCascading, boolean isConstrained) { this.name = name; this.type = type; this.directConstraints = CollectionHelper.toImmutableSet( directConstraints ); this.containerElementsConstraints = CollectionHelper.toImmutableSet( containerElementsConstraints ); this.allConstraints = Stream.concat( directConstraints.stream(), containerElementsConstraints.stream() ) .collect( Collectors.collectingAndThen( Collectors.toSet(), CollectionHelper::toImmutableSet ) ); this.constrainedMetaDataKind = constrainedMetaDataKind; this.isCascading = isCascading; this.isConstrained = isConstrained; } @Override public String getName() { return name; } @Override public Type getType() { return type; } @Override public Iterator<MetaConstraint<?>> iterator() { return allConstraints.iterator(); } public Set<MetaConstraint<?>> getAllConstraints() { return allConstraints; } public Set<MetaConstraint<?>> getDirectConstraints() { return directConstraints; } public Set<MetaConstraint<?>> getContainerElementsConstraints() { return containerElementsConstraints; } @Override public ElementKind getKind() { return constrainedMetaDataKind; } @Override public final boolean isCascading() { return isCascading; } @Override public boolean isConstrained() { return isConstrained; } @Override public String toString() { return "AbstractConstraintMetaData [name=" + name + ", type=" + type + ", constrainedMetaDataKind=" + constrainedMetaDataKind + ", directConstraints=" + directConstraints + ", containerElementsConstraints=" + containerElementsConstraints + ", isCascading=" + isCascading + ", isConstrained=" + isConstrained + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ( ( name == null ) ? 0 : name.hashCode() ); return result; } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } AbstractConstraintMetaData other = (AbstractConstraintMetaData) obj; if ( name == null ) { if ( other.name != null ) { return false; } } else if ( !name.equals( other.name ) ) { return false; } return true; } protected Set<ConstraintDescriptorImpl<?>> asDescriptors(Set<MetaConstraint<?>> constraints) { Set<ConstraintDescriptorImpl<?>> theValue = newHashSet(); for ( MetaConstraint<?> oneConstraint : constraints ) { theValue.add( oneConstraint.getDescriptor() ); } return theValue; } protected List<ContainerElementTypeDescriptor> asContainerElementTypeDescriptors( Set<MetaConstraint<?>> containerElementsConstraints, CascadingMetaData cascadingMetaData, boolean defaultGroupSequenceRedefined, List<Class<?>> defaultGroupSequence) { return asContainerElementTypeDescriptors( type, ContainerElementMetaConstraintTree.of( containerElementsConstraints ), cascadingMetaData, defaultGroupSequenceRedefined, defaultGroupSequence ); } private List<ContainerElementTypeDescriptor> asContainerElementTypeDescriptors(Type type, ContainerElementMetaConstraintTree containerElementMetaConstraintTree, CascadingMetaData cascadingMetaData, boolean defaultGroupSequenceRedefined, List<Class<?>> defaultGroupSequence) { if ( type instanceof ParameterizedType ) { List<ContainerElementTypeDescriptor> containerElementTypeDescriptors = new ArrayList<>(); Type[] typeArguments = ( (ParameterizedType) type ).getActualTypeArguments(); TypeVariable<?>[] typeParameters = ReflectionHelper.getClassFromType( type ).getTypeParameters(); for ( int i = 0; i < typeArguments.length; i++ ) { Type typeArgument = typeArguments[i]; TypeVariable<?> typeParameter = typeParameters[i]; Set<MetaConstraint<?>> constraints = Collections.emptySet(); ContainerElementMetaConstraintTree currentContainerElementMetaConstraintTree = null; if ( containerElementMetaConstraintTree != null && containerElementMetaConstraintTree.nodes.containsKey( typeParameter ) ) { currentContainerElementMetaConstraintTree = containerElementMetaConstraintTree.nodes.get( typeParameter ); constraints = containerElementMetaConstraintTree.nodes.get( typeParameter ).constraints; } CascadingMetaData currentCascadingMetaData = null; boolean cascading = false; Set<GroupConversionDescriptor> groupConversionDescriptors = Collections.emptySet(); if ( cascadingMetaData != null ) { for ( CascadingMetaData candidateCascadingMetaData : cascadingMetaData.getContainerElementTypesCascadingMetaData() ) { if ( candidateCascadingMetaData.getTypeParameter().equals( typeParameter ) ) { currentCascadingMetaData = candidateCascadingMetaData; cascading = currentCascadingMetaData.isCascading(); groupConversionDescriptors = currentCascadingMetaData.getGroupConversionDescriptors(); } } } containerElementTypeDescriptors.add( new ContainerElementTypeDescriptorImpl( typeArgument, TypeVariables.getTypeParameterIndex( typeParameter ), asDescriptors( constraints ), asContainerElementTypeDescriptors( typeArgument, currentContainerElementMetaConstraintTree, currentCascadingMetaData, defaultGroupSequenceRedefined, defaultGroupSequence ), cascading, defaultGroupSequenceRedefined, defaultGroupSequence, groupConversionDescriptors ) ); } return containerElementTypeDescriptors; } else if ( type instanceof GenericArrayType ) { // TODO Container element constraints are not supported for arrays in the Bean Validation specification. // Let's ignore them for now. return Collections.emptyList(); } else { return Collections.emptyList(); } } private static class ContainerElementMetaConstraintTree { private Map<TypeVariable<?>, ContainerElementMetaConstraintTree> nodes = new HashMap<>(); private Set<MetaConstraint<?>> constraints = new HashSet<>(); private static ContainerElementMetaConstraintTree of(Set<MetaConstraint<?>> containerElementsConstraints) { ContainerElementMetaConstraintTree containerElementMetaConstraintTree = new ContainerElementMetaConstraintTree(); for ( MetaConstraint<?> constraint : containerElementsConstraints ) { ConstraintLocation currentLocation = constraint.getLocation(); List<TypeVariable<?>> constraintPath = new ArrayList<>(); while ( currentLocation instanceof TypeArgumentConstraintLocation ) { TypeArgumentConstraintLocation typeArgumentConstraintLocation = ( (TypeArgumentConstraintLocation) currentLocation ); constraintPath.add( typeArgumentConstraintLocation.getTypeParameter() ); currentLocation = typeArgumentConstraintLocation.getDelegate(); } Collections.reverse( constraintPath ); containerElementMetaConstraintTree.addConstraint( constraintPath, constraint ); } return containerElementMetaConstraintTree; } private void addConstraint(List<TypeVariable<?>> path, MetaConstraint<?> constraint) { ContainerElementMetaConstraintTree tree = this; for ( TypeVariable<?> typeParameter : path ) { tree = tree.nodes.computeIfAbsent( typeParameter, tp -> new ContainerElementMetaConstraintTree() ); } tree.constraints.add( constraint ); } } }