diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidationContext.java index 0f0bc54625..6ed0997ab2 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidationContext.java @@ -6,7 +6,6 @@ */ package org.hibernate.validator.internal.engine; -import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap; import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; import java.lang.reflect.Executable; @@ -31,12 +30,12 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; -import org.hibernate.validator.internal.util.IdentitySet; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; @@ -91,21 +90,15 @@ public class ValidationContext { private final Object executableReturnValue; /** - * Maps a group to an identity set to keep track of already validated objects. We have to make sure - * that each object gets only validated once per group and property path. + * The set of already processed unit of works. See {@link ProcessedUnit}. */ - private final Map, IdentitySet> processedBeansPerGroup; + private final Set processedUnits; /** * Maps an object to a list of paths in which it has been validated. The objects are the bean instances. */ private final Map> processedPathsPerBean; - /** - * Maps processed constraints to the bean and path for which they have been processed. - */ - private final Map processedMetaConstraints; - /** * Contains all failing constraints so far. */ @@ -174,9 +167,8 @@ private ValidationContext(ConstraintValidatorManager constraintValidatorManager, this.executableParameters = executableParameters; this.executableReturnValue = executableReturnValue; - this.processedBeansPerGroup = newHashMap(); + this.processedUnits = new HashSet<>(); this.processedPathsPerBean = new IdentityHashMap<>(); - this.processedMetaConstraints = newHashMap(); this.failingConstraintViolations = newHashSet(); } @@ -294,7 +286,7 @@ public ConstraintViolation createConstraintViolation(ValueContext local constraintViolationCreationContext.getExpressionVariables() ); // at this point we make a copy of the path to avoid side effects - Path path = PathImpl.createCopy( constraintViolationCreationContext.getPath() ); + Path path = constraintViolationCreationContext.getPath().build(); Object dynamicPayload = constraintViolationCreationContext.getDynamicPayload(); if ( executableParameters != null ) { return ConstraintViolationImpl.forParameterValidation( @@ -349,22 +341,11 @@ else if ( executableReturnValue != null ) { } public boolean hasMetaConstraintBeenProcessed(Object bean, Path path, MetaConstraint metaConstraint) { - // TODO switch to proper multi key map (HF) - IdentitySet processedConstraints = processedMetaConstraints.get( new BeanAndPath( bean, path ) ); - return processedConstraints != null && processedConstraints.contains( metaConstraint ); + return processedUnits.contains( new BeanPathMetaConstraintProcessedUnit( bean, path, metaConstraint ) ); } public void markConstraintProcessed(Object bean, Path path, MetaConstraint metaConstraint) { - // TODO switch to proper multi key map (HF) - BeanAndPath beanAndPath = new BeanAndPath( bean, path ); - if ( processedMetaConstraints.containsKey( beanAndPath ) ) { - processedMetaConstraints.get( beanAndPath ).add( metaConstraint ); - } - else { - IdentitySet set = new IdentitySet(); - set.add( metaConstraint ); - processedMetaConstraints.put( beanAndPath, set ); - } + processedUnits.add( new BeanPathMetaConstraintProcessedUnit( bean, path, metaConstraint ) ); } public String getValidatedProperty() { @@ -443,33 +424,17 @@ private boolean isSubPathOf(Path p1, Path p2) { } private boolean isAlreadyValidatedForCurrentGroup(Object value, Class group) { - IdentitySet objectsProcessedInCurrentGroups = processedBeansPerGroup.get( group ); - return objectsProcessedInCurrentGroups != null && objectsProcessedInCurrentGroups.contains( value ); + return processedUnits.contains( new BeanGroupProcessedUnit( value, group ) ); } - private void markCurrentBeanAsProcessedForCurrentPath(Object value, PathImpl path) { + private void markCurrentBeanAsProcessedForCurrentPath(Object bean, PathBuilder path) { // HV-1031 The path object is mutated as we traverse the object tree, hence copy it before saving it - path = PathImpl.createCopy( path ); - - if ( processedPathsPerBean.containsKey( value ) ) { - processedPathsPerBean.get( value ).add( path ); - } - else { - Set set = new HashSet<>(); - set.add( path ); - processedPathsPerBean.put( value, set ); - } + processedPathsPerBean.computeIfAbsent( bean, b -> new HashSet<>() ) + .add( path.build() ); } - private void markCurrentBeanAsProcessedForCurrentGroup(Object value, Class group) { - if ( processedBeansPerGroup.containsKey( group ) ) { - processedBeansPerGroup.get( group ).add( value ); - } - else { - IdentitySet set = new IdentitySet(); - set.add( value ); - processedBeansPerGroup.put( group, set ); - } + private void markCurrentBeanAsProcessedForCurrentGroup(Object bean, Class group) { + processedUnits.add( new BeanGroupProcessedUnit( bean, group ) ); } /** @@ -609,15 +574,64 @@ public ValidationContext forValidateReturnValue( } } - private static final class BeanAndPath { - private final Object bean; - private final Path path; - private final int hashCode; + private static final class BeanGroupProcessedUnit { - private BeanAndPath(Object bean, Path path) { + // these fields are final but we don't mark them as final as an optimization + private Object bean; + private Class group; + private int hashCode; + + private BeanGroupProcessedUnit(Object bean, Class group) { + this.bean = bean; + this.group = group; + this.hashCode = createHashCode(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != BeanGroupProcessedUnit.class ) { + return false; + } + + BeanGroupProcessedUnit that = (BeanGroupProcessedUnit) o; + + if ( bean != that.bean ) { // instance equality + return false; + } + if ( !group.equals( that.group ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + + private int createHashCode() { + int result = System.identityHashCode( bean ); + result = 31 * result + group.hashCode(); + return result; + } + } + + private static final class BeanPathMetaConstraintProcessedUnit { + + // these fields are final but we don't mark them as final as an optimization + private Object bean; + private Path path; + private MetaConstraint metaConstraint; + private int hashCode; + + private BeanPathMetaConstraintProcessedUnit(Object bean, Path path, MetaConstraint metaConstraint) { this.bean = bean; this.path = path; - // pre-calculate hash code, the class is immutable and hashCode is needed often + this.metaConstraint = metaConstraint; this.hashCode = createHashCode(); } @@ -626,11 +640,11 @@ public boolean equals(Object o) { if ( this == o ) { return true; } - if ( o == null || getClass() != o.getClass() ) { + if ( o == null || getClass() != BeanPathMetaConstraintProcessedUnit.class ) { return false; } - BeanAndPath that = (BeanAndPath) o; + BeanPathMetaConstraintProcessedUnit that = (BeanPathMetaConstraintProcessedUnit) o; if ( bean != that.bean ) { // instance equality return false; @@ -638,6 +652,9 @@ public boolean equals(Object o) { if ( !path.equals( that.path ) ) { return false; } + if ( metaConstraint != that.metaConstraint ) { + return false; + } return true; } @@ -650,6 +667,7 @@ public int hashCode() { private int createHashCode() { int result = System.identityHashCode( bean ); result = 31 * result + path.hashCode(); + result = 31 * result + System.identityHashCode( metaConstraint ); return result; } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java index 64f6e1b3d9..4533956972 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java @@ -37,6 +37,7 @@ import javax.validation.valueextraction.ValueExtractor; import org.hibernate.validator.internal.engine.ValidationContext.ValidationContextBuilder; +import org.hibernate.validator.internal.engine.ValueContext.ValueState; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.groups.Group; import org.hibernate.validator.internal.engine.groups.GroupWithInheritance; @@ -44,7 +45,7 @@ import org.hibernate.validator.internal.engine.groups.ValidationOrder; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.path.NodeImpl; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.resolver.CachingTraversableResolverForSingleValidation; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper; @@ -177,7 +178,7 @@ public final Set> validate(T object, Class... grou parameterNameProvider, object, validationContext.getRootBeanMetaData(), - PathImpl.createRootPath() + PathBuilder.createRootPath() ); return validateInContext( validationContext, valueContext, validationOrder ); @@ -195,7 +196,7 @@ public final Set> validateProperty(T object, String p return Collections.emptySet(); } - PathImpl propertyPath = PathImpl.createPathFromString( propertyName ); + PathBuilder propertyPath = PathBuilder.createPathFromString( propertyName ); ValueContext valueContext = getValueContextForPropertyValidation( validationContext, propertyPath ); if ( valueContext.getCurrentBean() == null ) { @@ -224,7 +225,7 @@ public final Set> validateValue(Class beanType, St return validateValueInContext( validationContext, value, - PathImpl.createPathFromString( propertyName ), + PathBuilder.createPathFromString( propertyName ), validationOrder ); } @@ -551,11 +552,11 @@ private boolean validateMetaConstraint(ValidationContext validationContext, V success = metaConstraint.validateConstraint( validationContext, valueContext ); - validationContext.markConstraintProcessed( valueContext.getCurrentBean(), valueContext.getPropertyPath(), metaConstraint ); + validationContext.markConstraintProcessed( valueContext.getCurrentBean(), valueContext.getPropertyPath().build(), metaConstraint ); } // reset the value context to the state before this call - valueContext.resetValueState( originalValueState ); + valueContext.resetValueState( originalValueState, true ); return success; } @@ -600,13 +601,13 @@ private void validateCascadedConstraints(ValidationContext validationContext, } // reset the value context - valueContext.resetValueState( originalValueState ); + valueContext.resetValueState( originalValueState, true ); } } private void validateCascadedAnnotatedObjectForCurrentGroup(Object value, ValidationContext validationContext, ValueContext valueContext, CascadingMetaData cascadingMetaData) { - if ( validationContext.isBeanAlreadyValidated( value, valueContext.getCurrentGroup(), valueContext.getPropertyPath() ) || + if ( validationContext.isBeanAlreadyValidated( value, valueContext.getCurrentGroup(), valueContext.getPropertyPath().build() ) || shouldFailFast( validationContext ) ) { return; } @@ -670,21 +671,19 @@ public void iterableValue(String nodeName, Object value) { @Override public void indexedValue(String nodeName, int index, Object value) { - valueContext.markCurrentPropertyAsIterable(); - valueContext.setIndex( index ); + valueContext.markCurrentPropertyAsIterableAndSetIndex( index ); doValidate( value, nodeName ); } @Override public void keyedValue(String nodeName, Object key, Object value) { - valueContext.markCurrentPropertyAsIterable(); - valueContext.setKey( key ); + valueContext.markCurrentPropertyAsIterableAndSetKey( key ); doValidate( value, nodeName ); } private void doValidate(Object value, String nodeName) { if ( value == null || - validationContext.isBeanAlreadyValidated( value, valueContext.getCurrentGroup(), valueContext.getPropertyPath() ) || + validationContext.isBeanAlreadyValidated( value, valueContext.getCurrentGroup(), valueContext.getPropertyPath().build() ) || shouldFailFast( validationContext ) ) { return; } @@ -715,11 +714,15 @@ private void doValidate(Object value, String nodeName) { cascadedValueContext.setTypeParameter( cascadingMetaData.getDeclaredContainerClass(), cascadingMetaData.getDeclaredTypeParameter() ); } + ValueState valueState = cascadedTypeArgumentValueContext.getCurrentValueState(); + if ( nodeName != null ) { cascadedTypeArgumentValueContext.appendTypeParameterNode( nodeName ); } validateCascadedContainerElementsInContext( value, validationContext, cascadedTypeArgumentValueContext, cascadingMetaData, validationOrder ); + + cascadedTypeArgumentValueContext.resetValueState( valueState, nodeName != null ); } } } @@ -782,7 +785,7 @@ private ValueContext buildNewLocalExecutionContext(ValueContext return newValueContext; } - private Set> validateValueInContext(ValidationContext validationContext, Object value, PathImpl propertyPath, + private Set> validateValueInContext(ValidationContext validationContext, Object value, PathBuilder propertyPath, ValidationOrder validationOrder) { ValueContext valueContext = getValueContextForValueValidation( validationContext, propertyPath ); valueContext.setCurrentValidatedValue( value ); @@ -870,7 +873,7 @@ private void validateParametersInContext(ValidationContext validationCont parameterNameProvider, parameterValues, executableMetaData.getValidatableParametersMetaData(), - PathImpl.createPathForExecutable( executableMetaData ) + PathBuilder.createPathForExecutable( executableMetaData ) ); groupIterator = validationOrder.getGroupIterator(); @@ -1004,7 +1007,7 @@ private ValueContext getExecutableValueContext(T object, Executab parameterNameProvider, object, validatable, - PathImpl.createPathForExecutable( executableMetaData ) + PathBuilder.createPathForExecutable( executableMetaData ) ); } else { @@ -1013,7 +1016,7 @@ private ValueContext getExecutableValueContext(T object, Executab parameterNameProvider, (Class) null, //the type is not required in this case (only for cascaded validation) validatable, - PathImpl.createPathForExecutable( executableMetaData ) + PathBuilder.createPathForExecutable( executableMetaData ) ); } @@ -1056,7 +1059,7 @@ private void validateReturnValueInContext(ValidationContext validation parameterNameProvider, value, executableMetaData.getReturnValueMetaData(), - PathImpl.createPathForExecutable( executableMetaData ) + PathBuilder.createPathForExecutable( executableMetaData ) ); groupIterator = validationOrder.getGroupIterator(); @@ -1161,7 +1164,7 @@ private void validateReturnValueForSingleGroup(ValidationContext validati * @return Returns an instance of {@code ValueContext} which describes the local validation context associated to * the given property path. */ - private ValueContext getValueContextForPropertyValidation(ValidationContext validationContext, PathImpl propertyPath) { + private ValueContext getValueContextForPropertyValidation(ValidationContext validationContext, PathBuilder propertyPath) { Class clazz = validationContext.getRootBeanClass(); BeanMetaData beanMetaData = validationContext.getRootBeanMetaData(); Object value = validationContext.getRootBean(); @@ -1239,7 +1242,7 @@ else if ( propertyPathNode.getKey() != null ) { * the given property path. */ private ValueContext getValueContextForValueValidation(ValidationContext validationContext, - PathImpl propertyPath) { + PathBuilder propertyPath) { Class clazz = validationContext.getRootBeanClass(); BeanMetaData beanMetaData = null; PropertyMetaData propertyMetaData = null; @@ -1300,7 +1303,7 @@ private boolean isValidationRequired(ValidationContext validationContext, } if ( validationContext.hasMetaConstraintBeenProcessed( valueContext.getCurrentBean(), - valueContext.getPropertyPath(), + valueContext.getPropertyPath().build(), metaConstraint ) ) { return false; @@ -1317,7 +1320,7 @@ private boolean isValidationRequired(ValidationContext validationContext, ); } - private boolean isReachable(ValidationContext validationContext, Object traversableObject, PathImpl path, ElementType type) { + private boolean isReachable(ValidationContext validationContext, Object traversableObject, PathBuilder path, ElementType type) { if ( needToCallTraversableResolver( path, type ) ) { return true; } @@ -1337,7 +1340,7 @@ private boolean isReachable(ValidationContext validationContext, Object trave } } - private boolean needToCallTraversableResolver(PathImpl path, ElementType type) { + private boolean needToCallTraversableResolver(PathBuilder path, ElementType type) { // as the TraversableResolver interface is designed right now it does not make sense to call it when // there is no traversable object hosting the property to be accessed. For this reason we don't call the resolver // for class level constraints (ElementType.TYPE) or top level method parameters or return values. @@ -1348,7 +1351,7 @@ private boolean needToCallTraversableResolver(PathImpl path, ElementType type) { || isReturnValueValidation( path ); } - private boolean isCascadeRequired(ValidationContext validationContext, Object traversableObject, PathImpl path, ElementType type) { + private boolean isCascadeRequired(ValidationContext validationContext, Object traversableObject, PathBuilder path, ElementType type) { if ( needToCallTraversableResolver( path, type ) ) { return true; } @@ -1377,15 +1380,15 @@ private boolean isClassLevelConstraint(ElementType type) { return ElementType.TYPE.equals( type ); } - private boolean isCrossParameterValidation(PathImpl path) { + private boolean isCrossParameterValidation(PathBuilder path) { return path.getLeafNode().getKind() == ElementKind.CROSS_PARAMETER; } - private boolean isParameterValidation(PathImpl path) { + private boolean isParameterValidation(PathBuilder path) { return path.getLeafNode().getKind() == ElementKind.PARAMETER; } - private boolean isReturnValueValidation(PathImpl path) { + private boolean isReturnValueValidation(PathBuilder path) { return path.getLeafNode().getKind() == ElementKind.RETURN_VALUE; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java index 7d3ab3f056..c08c0ba7b4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java @@ -11,7 +11,7 @@ import javax.validation.groups.Default; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; @@ -52,7 +52,7 @@ public class ValueContext { /** * The current property path we are validating. */ - private PathImpl propertyPath; + private final PathBuilder propertyPath; /** * The current group we are validating. @@ -72,7 +72,7 @@ public class ValueContext { private ElementType elementType; public static ValueContext getLocalExecutionContext(BeanMetaDataManager beanMetaDataManager, - ExecutableParameterNameProvider parameterNameProvider, T value, Validatable validatable, PathImpl propertyPath) { + ExecutableParameterNameProvider parameterNameProvider, T value, Validatable validatable, PathBuilder propertyPath) { @SuppressWarnings("unchecked") Class rootBeanType = (Class) value.getClass(); return new ValueContext<>( parameterNameProvider, value, rootBeanType, beanMetaDataManager.getBeanMetaData( rootBeanType ), validatable, propertyPath ); @@ -80,24 +80,24 @@ public static ValueContext getLocalExecutionContext(BeanMetaDataMan @SuppressWarnings("unchecked") public static ValueContext getLocalExecutionContext(ExecutableParameterNameProvider parameterNameProvider, T value, - BeanMetaData currentBeanMetaData, PathImpl propertyPath) { + BeanMetaData currentBeanMetaData, PathBuilder propertyPath) { Class rootBeanType = (Class) value.getClass(); return new ValueContext<>( parameterNameProvider, value, rootBeanType, (BeanMetaData) currentBeanMetaData, currentBeanMetaData, propertyPath ); } public static ValueContext getLocalExecutionContext(BeanMetaDataManager beanMetaDataManager, - ExecutableParameterNameProvider parameterNameProvider, Class rootBeanType, Validatable validatable, PathImpl propertyPath) { + ExecutableParameterNameProvider parameterNameProvider, Class rootBeanType, Validatable validatable, PathBuilder propertyPath) { BeanMetaData rootBeanMetaData = rootBeanType != null ? beanMetaDataManager.getBeanMetaData( rootBeanType ) : null; return new ValueContext<>( parameterNameProvider, null, rootBeanType, rootBeanMetaData, validatable, propertyPath ); } @SuppressWarnings("unchecked") public static ValueContext getLocalExecutionContext(ExecutableParameterNameProvider parameterNameProvider, Class currentBeanType, - BeanMetaData currentBeanMetaData, PathImpl propertyPath) { + BeanMetaData currentBeanMetaData, PathBuilder propertyPath) { return new ValueContext<>( parameterNameProvider, null, currentBeanType, (BeanMetaData) currentBeanMetaData, currentBeanMetaData, propertyPath ); } - private ValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, Class currentBeanType, BeanMetaData currentBeanMetaData, Validatable validatable, PathImpl propertyPath) { + private ValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, Class currentBeanType, BeanMetaData currentBeanMetaData, Validatable validatable, PathBuilder propertyPath) { this.parameterNameProvider = parameterNameProvider; this.currentBean = currentBean; this.currentBeanType = currentBeanType; @@ -106,7 +106,7 @@ private ValueContext(ExecutableParameterNameProvider parameterNameProvider, T cu this.propertyPath = propertyPath; } - public final PathImpl getPropertyPath() { + public final PathBuilder getPropertyPath() { return propertyPath; } @@ -138,33 +138,27 @@ public final Object getCurrentValidatedValue() { } public final void appendNode(Cascadable node) { - PathImpl newPath = PathImpl.createCopy( propertyPath ); - node.appendTo( newPath ); - propertyPath = newPath; + node.appendTo( propertyPath ); } public final void appendNode(ConstraintLocation location) { - PathImpl newPath = PathImpl.createCopy( propertyPath ); - location.appendTo( parameterNameProvider, newPath ); - propertyPath = newPath; + location.appendTo( parameterNameProvider, propertyPath ); } public final void appendTypeParameterNode(String nodeName) { - PathImpl newPath = PathImpl.createCopy( propertyPath ); - newPath.addContainerElementNode( nodeName ); - propertyPath = newPath; + propertyPath.addContainerElementNode( nodeName ); } public final void markCurrentPropertyAsIterable() { propertyPath.makeLeafNodeIterable(); } - public final void setKey(Object key) { - propertyPath.setLeafNodeMapKey( key ); + public final void markCurrentPropertyAsIterableAndSetKey(Object key) { + propertyPath.makeLeafNodeIterableAndSetMapKey( key ); } - public final void setIndex(Integer index) { - propertyPath.setLeafNodeIndex( index ); + public final void markCurrentPropertyAsIterableAndSetIndex(Integer index) { + propertyPath.makeLeafNodeIterableAndSetIndex( index ); } /** @@ -192,7 +186,7 @@ public final void setCurrentGroup(Class currentGroup) { } public final void setCurrentValidatedValue(V currentValue) { - propertyPath.setLeafNodeValue( currentValue ); + propertyPath.setLeafNodeValueIfRequired( currentValue ); this.currentValue = currentValue; } @@ -209,11 +203,13 @@ public final void setElementType(ElementType elementType) { } public final ValueState getCurrentValueState() { - return new ValueState( propertyPath, currentValue ); + return new ValueState<>( currentValue ); } - public final void resetValueState(ValueState valueState) { - this.propertyPath = valueState.propertyPath; + public final void resetValueState(ValueState valueState, boolean resetPath) { + if ( resetPath ) { + this.propertyPath.removeLeafNode(); + } this.currentValue = valueState.currentValue; } @@ -238,12 +234,9 @@ public Object getValue(Object parent, ConstraintLocation location) { public static class ValueState { - private final PathImpl propertyPath; - private final V currentValue; - private ValueState(PathImpl propertyPath, V currentValue) { - this.propertyPath = propertyPath; + private ValueState(V currentValue) { this.currentValue = currentValue; } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintValidatorContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintValidatorContextImpl.java index 30cca9ff14..9bab4988a5 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintValidatorContextImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintValidatorContextImpl.java @@ -6,8 +6,6 @@ */ package org.hibernate.validator.internal.engine.constraintvalidation; -import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -29,7 +27,8 @@ import javax.validation.metadata.ConstraintDescriptor; import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; +import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; @@ -47,13 +46,13 @@ public class ConstraintValidatorContextImpl implements HibernateConstraintValida private Map expressionVariables; private final List methodParameterNames; private final ClockProvider clockProvider; - private final List constraintViolationCreationContexts = newArrayList( 3 ); - private final PathImpl basePath; + private final PathBuilder basePath; private final ConstraintDescriptor constraintDescriptor; + private List constraintViolationCreationContexts; private boolean defaultDisabled; private Object dynamicPayload; - public ConstraintValidatorContextImpl(List methodParameterNames, ClockProvider clockProvider, PathImpl propertyPath, + public ConstraintValidatorContextImpl(List methodParameterNames, ClockProvider clockProvider, PathBuilder propertyPath, ConstraintDescriptor constraintDescriptor) { this.methodParameterNames = methodParameterNames; this.clockProvider = clockProvider; @@ -76,7 +75,7 @@ public final ConstraintViolationBuilder buildConstraintViolationWithTemplate(Str return new ConstraintViolationBuilderImpl( methodParameterNames, messageTemplate, - PathImpl.createCopy( basePath ) + basePath ); } @@ -129,25 +128,34 @@ public final ConstraintDescriptor getConstraintDescriptor() { } public final List getConstraintViolationCreationContexts() { - if ( defaultDisabled && constraintViolationCreationContexts.size() == 0 ) { - throw log.getAtLeastOneCustomMessageMustBeCreatedException(); + if ( defaultDisabled ) { + if ( constraintViolationCreationContexts == null || constraintViolationCreationContexts.size() == 0 ) { + throw log.getAtLeastOneCustomMessageMustBeCreatedException(); + } + + return CollectionHelper.toImmutableList( constraintViolationCreationContexts ); } - List returnedConstraintViolationCreationContexts = new ArrayList<>( - constraintViolationCreationContexts - ); - if ( !defaultDisabled ) { - returnedConstraintViolationCreationContexts.add( - new ConstraintViolationCreationContext( - getDefaultConstraintMessageTemplate(), - basePath, - messageParameters != null ? new HashMap<>( messageParameters ) : Collections.emptyMap(), - expressionVariables != null ? new HashMap<>( expressionVariables ) : Collections.emptyMap(), - dynamicPayload - ) - ); + if ( constraintViolationCreationContexts == null || constraintViolationCreationContexts.size() == 0 ) { + return Collections.singletonList( getDefaultConstraintViolationCreationContext() ); } - return returnedConstraintViolationCreationContexts; + + List returnedConstraintViolationCreationContexts = + new ArrayList<>( constraintViolationCreationContexts.size() + 1 ); + returnedConstraintViolationCreationContexts.addAll( constraintViolationCreationContexts ); + returnedConstraintViolationCreationContexts.add( getDefaultConstraintViolationCreationContext() ); + + return CollectionHelper.toImmutableList( returnedConstraintViolationCreationContexts ); + } + + private ConstraintViolationCreationContext getDefaultConstraintViolationCreationContext() { + return new ConstraintViolationCreationContext( + getDefaultConstraintMessageTemplate(), + basePath, + messageParameters != null ? new HashMap<>( messageParameters ) : Collections.emptyMap(), + expressionVariables != null ? new HashMap<>( expressionVariables ) : Collections.emptyMap(), + dynamicPayload + ); } public List getMethodParameterNames() { @@ -156,14 +164,17 @@ public List getMethodParameterNames() { private abstract class NodeBuilderBase { protected final String messageTemplate; - protected PathImpl propertyPath; + protected PathBuilder propertyPath; - protected NodeBuilderBase(String template, PathImpl path) { + protected NodeBuilderBase(String template, PathBuilder path) { this.messageTemplate = template; this.propertyPath = path; } public ConstraintValidatorContext addConstraintViolation() { + if ( constraintViolationCreationContexts == null ) { + constraintViolationCreationContexts = CollectionHelper.newArrayList( 3 ); + } constraintViolationCreationContexts.add( new ConstraintViolationCreationContext( messageTemplate, @@ -181,7 +192,7 @@ private class ConstraintViolationBuilderImpl extends NodeBuilderBase implements private final List methodParameterNames; - private ConstraintViolationBuilderImpl(List methodParameterNames, String template, PathImpl path) { + private ConstraintViolationBuilderImpl(List methodParameterNames, String template, PathBuilder path) { super( template, path ); this.methodParameterNames = methodParameterNames; } @@ -241,7 +252,7 @@ private void dropLeafNodeIfRequired() { private class NodeBuilder extends NodeBuilderBase implements NodeBuilderDefinedContext, LeafNodeBuilderDefinedContext, ContainerElementNodeBuilderDefinedContext { - private NodeBuilder(String template, PathImpl path) { + private NodeBuilder(String template, PathBuilder path) { super( template, path ); } @@ -279,7 +290,7 @@ private class DeferredNodeBuilder extends NodeBuilderBase private final Integer leafNodeTypeArgumentIndex; - private DeferredNodeBuilder(String template, PathImpl path, String nodeName, ElementKind leafNodeKind) { + private DeferredNodeBuilder(String template, PathBuilder path, String nodeName, ElementKind leafNodeKind) { super( template, path ); this.leafNodeName = nodeName; this.leafNodeKind = leafNodeKind; @@ -287,7 +298,7 @@ private DeferredNodeBuilder(String template, PathImpl path, String nodeName, Ele this.leafNodeTypeArgumentIndex = null; } - private DeferredNodeBuilder(String template, PathImpl path, String nodeName, Class leafNodeContainerType, Integer leafNodeTypeArgumentIndex) { + private DeferredNodeBuilder(String template, PathBuilder path, String nodeName, Class leafNodeContainerType, Integer leafNodeTypeArgumentIndex) { super( template, path ); this.leafNodeName = nodeName; this.leafNodeKind = ElementKind.CONTAINER_ELEMENT; @@ -309,14 +320,14 @@ public DeferredNodeBuilder inContainer(Class containerClass, Integer typeArgu @Override public NodeBuilder atKey(Object key) { - propertyPath.setLeafNodeMapKey( key ); + propertyPath.makeLeafNodeIterableAndSetMapKey( key ); addLeafNode(); return new NodeBuilder( messageTemplate, propertyPath ); } @Override public NodeBuilder atIndex(Integer index) { - propertyPath.setLeafNodeIndex( index ); + propertyPath.makeLeafNodeIterableAndSetIndex( index ); addLeafNode(); return new NodeBuilder( messageTemplate, propertyPath ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintViolationCreationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintViolationCreationContext.java index ce0396a78a..c2474614bd 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintViolationCreationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintViolationCreationContext.java @@ -11,7 +11,7 @@ import java.util.Collections; import java.util.Map; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.stereotypes.Immutable; /** @@ -21,18 +21,18 @@ */ public class ConstraintViolationCreationContext { private final String message; - private final PathImpl propertyPath; + private final PathBuilder propertyPath; @Immutable private final Map messageParameters; @Immutable private final Map expressionVariables; private final Object dynamicPayload; - public ConstraintViolationCreationContext(String message, PathImpl property) { + public ConstraintViolationCreationContext(String message, PathBuilder property) { this( message, property, Collections.emptyMap(), Collections.emptyMap(), null ); } - public ConstraintViolationCreationContext(String message, PathImpl property, Map messageParameters, Map expressionVariables, + public ConstraintViolationCreationContext(String message, PathBuilder property, Map messageParameters, Map expressionVariables, Object dynamicPayload) { this.message = message; this.propertyPath = property; @@ -45,7 +45,7 @@ public final String getMessage() { return message; } - public final PathImpl getPath() { + public final PathBuilder getPath() { return propertyPath; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/groups/DefaultValidationOrder.java b/engine/src/main/java/org/hibernate/validator/internal/engine/groups/DefaultValidationOrder.java index b0f5471bb0..fbc8779593 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/groups/DefaultValidationOrder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/groups/DefaultValidationOrder.java @@ -7,21 +7,22 @@ package org.hibernate.validator.internal.engine.groups; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; + import javax.validation.GroupDefinitionException; +import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; -import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList; -import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap; - /** * An instance of {@code ValidationOrder} defines the group order during one validation call. * * @author Hardy Ferentschik + * @author Guillaume Smet */ public final class DefaultValidationOrder implements ValidationOrder { private static final Log log = LoggerFactory.make(); @@ -29,25 +30,35 @@ public final class DefaultValidationOrder implements ValidationOrder { /** * The list of single groups to be used this validation. */ - private List groupList = newArrayList(); + private List groupList; /** * The different sequences for this validation. The map contains the sequences mapped to their sequence * name. */ - private Map, Sequence> sequenceMap = newHashMap(); + private Map, Sequence> sequenceMap; @Override public Iterator getGroupIterator() { + if ( groupList == null ) { + return Collections.emptyIterator(); + } return groupList.iterator(); } @Override public Iterator getSequenceIterator() { + if ( sequenceMap == null ) { + return Collections.emptyIterator(); + } return sequenceMap.values().iterator(); } public void insertGroup(Group group) { + if ( groupList == null ) { + groupList = new ArrayList<>( 5 ); + } + if ( !groupList.contains( group ) ) { groupList.add( group ); } @@ -58,9 +69,11 @@ public void insertSequence(Sequence sequence) { return; } - if ( !sequenceMap.containsKey( sequence.getDefiningClass() ) ) { - sequenceMap.put( sequence.getDefiningClass(), sequence ); + if ( sequenceMap == null ) { + sequenceMap = CollectionHelper.newHashMap( 5 ); } + + sequenceMap.putIfAbsent( sequence.getDefiningClass(), sequence ); } @Override @@ -83,6 +96,10 @@ public String toString() { @Override public void assertDefaultGroupSequenceIsExpandable(List> defaultGroupSequence) throws GroupDefinitionException { + if ( sequenceMap == null ) { + return; + } + for ( Map.Entry, Sequence> entry : sequenceMap.entrySet() ) { List sequenceGroups = entry.getValue().getComposingGroups(); int defaultGroupIndex = sequenceGroups.indexOf( Group.DEFAULT_GROUP ); diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrder.java b/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrder.java index 2cff2487bb..4a9f634a69 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrder.java @@ -16,6 +16,7 @@ * Interface defining the methods needed to execute groups and sequences in the right order. * * @author Hardy Ferentschik + * @author Guillaume Smet */ public interface ValidationOrder { @@ -35,17 +36,22 @@ public interface ValidationOrder { void assertDefaultGroupSequenceIsExpandable(List> defaultGroupSequence) throws GroupDefinitionException; + /** + * A {@link org.hibernate.validator.internal.engine.groups.ValidationOrder} which contains a single group, {@code Default}. + */ + ValidationOrder DEFAULT_GROUP = new DefaultGroupValidationOrder(); + /** * A {@link org.hibernate.validator.internal.engine.groups.ValidationOrder} which contains a single sequence which * in turn contains a single group, {@code Default}. */ - ValidationOrder DEFAULT_SEQUENCE = new DefaultValidationOrder(); + ValidationOrder DEFAULT_SEQUENCE = new DefaultSequenceValidationOrder(); - class DefaultValidationOrder implements ValidationOrder { + class DefaultSequenceValidationOrder implements ValidationOrder { private final List defaultSequences; - private DefaultValidationOrder() { + private DefaultSequenceValidationOrder() { defaultSequences = Collections.singletonList( Sequence.DEFAULT ); } @@ -63,4 +69,27 @@ public Iterator getSequenceIterator() { public void assertDefaultGroupSequenceIsExpandable(List> defaultGroupSequence) throws GroupDefinitionException { } } + + class DefaultGroupValidationOrder implements ValidationOrder { + + private final List defaultGroups; + + private DefaultGroupValidationOrder() { + defaultGroups = Collections.singletonList( Group.DEFAULT_GROUP ); + } + + @Override + public Iterator getGroupIterator() { + return defaultGroups.iterator(); + } + + @Override + public Iterator getSequenceIterator() { + return Collections.emptyIterator(); + } + + @Override + public void assertDefaultGroupSequenceIsExpandable(List> defaultGroupSequence) throws GroupDefinitionException { + } + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrderGenerator.java b/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrderGenerator.java index 1f8fafe83e..d42ce32e74 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrderGenerator.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/groups/ValidationOrderGenerator.java @@ -31,13 +31,6 @@ public class ValidationOrderGenerator { private final ConcurrentMap, Sequence> resolvedSequences = new ConcurrentHashMap, Sequence>(); - private final DefaultValidationOrder validationOrderForDefaultGroup; - - public ValidationOrderGenerator() { - validationOrderForDefaultGroup = new DefaultValidationOrder(); - validationOrderForDefaultGroup.insertGroup( Group.DEFAULT_GROUP ); - } - /** * Creates a {@link ValidationOrder} for the given validation group. * @@ -49,6 +42,10 @@ public ValidationOrderGenerator() { * @return a {@link ValidationOrder} for the given validation group */ public ValidationOrder getValidationOrder(Class group, boolean expand) { + if ( Default.class.equals( group ) ) { + return ValidationOrder.DEFAULT_GROUP; + } + if ( expand ) { return getValidationOrder( Arrays.>asList( group ) ); } @@ -74,7 +71,7 @@ public ValidationOrder getValidationOrder(Collection> groups) { // HV-621 - if we deal with the Default group we return the default ValidationOrder. No need to // process Default as other groups which saves several reflection calls (HF) if ( groups.size() == 1 && groups.contains( Default.class ) ) { - return validationOrderForDefaultGroup; + return ValidationOrder.DEFAULT_GROUP; } for ( Class clazz : groups ) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/path/NodeImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/path/NodeImpl.java index 690c310bb1..8a93b9f7fa 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/path/NodeImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/path/NodeImpl.java @@ -60,7 +60,6 @@ public class NodeImpl private final Integer index; private final Object key; private final ElementKind kind; - private final int hashCode; //type-specific attributes private final Class[] parameterTypes; @@ -69,6 +68,7 @@ public class NodeImpl private final Class containerClass; private final Integer typeArgumentIndex; + private int hashCode = -1; private String asString; private NodeImpl(String name, NodeImpl parent, boolean isIterable, Integer index, Object key, ElementKind kind, Class[] parameterTypes, @@ -84,7 +84,6 @@ private NodeImpl(String name, NodeImpl parent, boolean isIterable, Integer index this.parameterIndex = parameterIndex; this.containerClass = containerClass; this.typeArgumentIndex = typeArgumentIndex; - this.hashCode = buildHashCode(); } //TODO It would be nicer if we could return PropertyNode @@ -208,7 +207,7 @@ public static NodeImpl makeIterable(NodeImpl node) { ); } - public static NodeImpl setIndex(NodeImpl node, Integer index) { + public static NodeImpl makeIterableAndSetIndex(NodeImpl node, Integer index) { return new NodeImpl( node.name, node.parent, @@ -224,7 +223,7 @@ public static NodeImpl setIndex(NodeImpl node, Integer index) { ); } - public static NodeImpl setMapKey(NodeImpl node, Object key) { + public static NodeImpl makeIterableAndSetMapKey(NodeImpl node, Object key) { return new NodeImpl( node.name, node.parent, @@ -440,7 +439,6 @@ public final int buildHashCode() { result = prime * result + ( ( name == null ) ? 0 : name.hashCode() ); result = prime * result + ( ( parameterIndex == null ) ? 0 : parameterIndex.hashCode() ); result = prime * result + ( ( parameterTypes == null ) ? 0 : Arrays.hashCode( parameterTypes ) ); - result = prime * result + ( ( parent == null ) ? 0 : parent.hashCode() ); result = prime * result + ( ( containerClass == null ) ? 0 : containerClass.hashCode() ); result = prime * result + ( ( typeArgumentIndex == null ) ? 0 : typeArgumentIndex.hashCode() ); return result; @@ -448,6 +446,10 @@ public final int buildHashCode() { @Override public int hashCode() { + if ( hashCode == -1 ) { + hashCode = buildHashCode(); + } + return hashCode; } @@ -525,14 +527,6 @@ else if ( !parameterIndex.equals( other.parameterIndex ) ) { else if ( !Arrays.equals( parameterTypes, other.parameterTypes ) ) { return false; } - if ( parent == null ) { - if ( other.parent != null ) { - return false; - } - } - else if ( !parent.equals( other.parent ) ) { - return false; - } return true; } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathBuilder.java new file mode 100644 index 0000000000..45ca0768b4 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathBuilder.java @@ -0,0 +1,374 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.engine.path; + +import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.validation.ElementKind; +import javax.validation.Path; + +import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; +import org.hibernate.validator.internal.util.Contracts; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; + +/** + * Default implementation of {@code javax.validation.Path}. + * + * @author Hardy Ferentschik + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + */ +public final class PathBuilder implements Path { + private static final Log log = LoggerFactory.make(); + + static final String PROPERTY_PATH_SEPARATOR = "."; + + /** + * Regular expression used to split a string path into its elements. + * + * @see Regular expression tester + */ + private static final String LEADING_PROPERTY_GROUP = "[^\\[\\.]+"; // everything up to a [ or . + private static final String OPTIONAL_INDEX_GROUP = "\\[(\\w*)\\]"; + private static final String REMAINING_PROPERTY_STRING = "\\.(.*)"; // processed recursively + + private static final Pattern PATH_PATTERN = Pattern.compile( "(" + LEADING_PROPERTY_GROUP + ")(" + OPTIONAL_INDEX_GROUP + ")?(" + REMAINING_PROPERTY_STRING + ")*" ); + private static final int PROPERTY_NAME_GROUP = 1; + private static final int INDEXED_GROUP = 2; + private static final int INDEX_GROUP = 3; + private static final int REMAINING_STRING_GROUP = 5; + + private final List nodeList; + private NodeImpl currentLeafNode; + + /** + * Returns a {@code Path} instance representing the path described by the + * given string. To create a root node the empty string should be passed. + * + * @param propertyPath the path as string representation. + * + * @return a {@code Path} instance representing the path described by the + * given string. + * + * @throws IllegalArgumentException in case {@code property == null} or + * {@code property} cannot be parsed. + */ + public static PathBuilder createPathFromString(String propertyPath) { + Contracts.assertNotNull( propertyPath, MESSAGES.propertyPathCannotBeNull() ); + + if ( propertyPath.length() == 0 ) { + return createRootPath(); + } + + return parseProperty( propertyPath ); + } + + public static PathBuilder createPathForExecutable(ExecutableMetaData executable) { + Contracts.assertNotNull( executable, "A method is required to create a method return value path." ); + + PathBuilder path = createRootPath(); + + if ( executable.getKind() == ElementKind.CONSTRUCTOR ) { + path.addConstructorNode( executable.getName(), executable.getParameterTypes() ); + } + else { + path.addMethodNode( executable.getName(), executable.getParameterTypes() ); + } + + return path; + } + + public static PathBuilder createRootPath() { + PathBuilder path = new PathBuilder(); + path.addBeanNode(); + return path; + } + + public PathBuilder getPathWithoutLeafNode() { + return new PathBuilder( new ArrayList<>( nodeList.subList( 0, nodeList.size() - 1 ) ) ); + } + + public NodeImpl addPropertyNode(String nodeName) { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createPropertyNode( nodeName, parent ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl addContainerElementNode(String nodeName) { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createContainerElementNode( nodeName, parent ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl addParameterNode(String nodeName, int index) { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createParameterNode( nodeName, parent, index ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl addCrossParameterNode() { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createCrossParameterNode( parent ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl addBeanNode() { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createBeanNode( parent ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl addReturnValueNode() { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createReturnValue( parent ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + private NodeImpl addConstructorNode(String name, Class[] parameterTypes) { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createConstructorNode( name, parent, parameterTypes ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + private NodeImpl addMethodNode(String name, Class[] parameterTypes) { + NodeImpl parent = currentLeafNode; + currentLeafNode = NodeImpl.createMethodNode( name, parent, parameterTypes ); + nodeList.add( currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl makeLeafNodeIterable() { + currentLeafNode = NodeImpl.makeIterable( currentLeafNode ); + + nodeList.set( nodeList.size() - 1, currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { + currentLeafNode = NodeImpl.makeIterableAndSetIndex( currentLeafNode, index ); + + nodeList.set( nodeList.size() - 1, currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) { + currentLeafNode = NodeImpl.makeIterableAndSetMapKey( currentLeafNode, key ); + + nodeList.set( nodeList.size() - 1, currentLeafNode ); + return currentLeafNode; + } + + public NodeImpl setLeafNodeValueIfRequired(Object value) { + // The value is only exposed for property and container element nodes + if ( currentLeafNode.getKind() == ElementKind.PROPERTY || currentLeafNode.getKind() == ElementKind.CONTAINER_ELEMENT ) { + currentLeafNode = NodeImpl.setPropertyValue( currentLeafNode, value ); + + nodeList.set( nodeList.size() - 1, currentLeafNode ); + } + return currentLeafNode; + } + + public NodeImpl setLeafNodeTypeParameter(Class containerClass, Integer typeArgumentIndex) { + currentLeafNode = NodeImpl.setTypeParameter( currentLeafNode, containerClass, typeArgumentIndex ); + + nodeList.set( nodeList.size() - 1, currentLeafNode ); + return currentLeafNode; + } + + public void removeLeafNode() { + if ( !nodeList.isEmpty() ) { + nodeList.remove( nodeList.size() - 1 ); + currentLeafNode = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); + } + } + + public NodeImpl getLeafNode() { + return currentLeafNode; + } + + @Override + public Iterator iterator() { + if ( nodeList.size() == 0 ) { + return Collections.emptyList().iterator(); + } + if ( nodeList.size() == 1 ) { + return nodeList.iterator(); + } + return nodeList.subList( 1, nodeList.size() ).iterator(); + } + + public String asString() { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for ( int i = 1; i < nodeList.size(); i++ ) { + NodeImpl nodeImpl = (NodeImpl) nodeList.get( i ); + String name = nodeImpl.asString(); + if ( name.isEmpty() ) { + // skip the node if it does not contribute to the string representation of the path, eg class level constraints + continue; + } + + if ( !first ) { + builder.append( PROPERTY_PATH_SEPARATOR ); + } + + builder.append( nodeImpl.asString() ); + + first = false; + } + return builder.toString(); + } + +// private void requiresWriteableNodeList() { +// if ( !nodeListRequiresCopy ) { +// return; +// } +// +// // Usually, the write operation is about adding one more node, so let's make the list one element larger. +// List newNodeList = new ArrayList<>( nodeList.size() + 1 ); +// newNodeList.addAll( nodeList ); +// nodeList = newNodeList; +// nodeListRequiresCopy = false; +// } + + @Override + public String toString() { + return asString(); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + PathBuilder other = (PathBuilder) obj; + if ( nodeList == null ) { + if ( other.nodeList != null ) { + return false; + } + } + else if ( !nodeList.equals( other.nodeList ) ) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ( ( nodeList == null ) ? 0 : nodeList.hashCode() ); + return result; + } + + public PathImpl build() { + return new PathImpl( new ArrayList<>( nodeList ) ); + } + + private PathBuilder() { + nodeList = new ArrayList<>( 5 ); + } + + private PathBuilder(List nodeList) { + this.nodeList = nodeList; + currentLeafNode = (NodeImpl) nodeList.get( nodeList.size() - 1 ); + } + + private static PathBuilder parseProperty(String propertyName) { + PathBuilder path = createRootPath(); + String tmp = propertyName; + do { + Matcher matcher = PATH_PATTERN.matcher( tmp ); + if ( matcher.matches() ) { + + String value = matcher.group( PROPERTY_NAME_GROUP ); + if ( !isValidJavaIdentifier( value ) ) { + throw log.getInvalidJavaIdentifierException( value ); + } + + // create the node + path.addPropertyNode( value ); + + // is the node indexable + if ( matcher.group( INDEXED_GROUP ) != null ) { + path.makeLeafNodeIterable(); + } + + // take care of the index/key if one exists + String indexOrKey = matcher.group( INDEX_GROUP ); + if ( indexOrKey != null && indexOrKey.length() > 0 ) { + try { + Integer i = Integer.parseInt( indexOrKey ); + path.makeLeafNodeIterableAndSetIndex( i ); + } + catch (NumberFormatException e) { + path.makeLeafNodeIterableAndSetMapKey( indexOrKey ); + } + } + + // match the remaining string + tmp = matcher.group( REMAINING_STRING_GROUP ); + } + else { + throw log.getUnableToParsePropertyPathException( propertyName ); + } + } while ( tmp != null ); + + if ( path.getLeafNode().isIterable() ) { + path.addBeanNode(); + } + + return path; + } + + /** + * Validate that the given identifier is a valid Java identifier according to the Java Language Specification, + * chapter 3.8 + * + * @param identifier string identifier to validate + * + * @return true if the given identifier is a valid Java Identifier + * + * @throws IllegalArgumentException if the given identifier is {@code null} + */ + private static boolean isValidJavaIdentifier(String identifier) { + Contracts.assertNotNull( identifier, "identifier param cannot be null" ); + + if ( identifier.length() == 0 || !Character.isJavaIdentifierStart( (int) identifier.charAt( 0 ) ) ) { + return false; + } + + for ( int i = 1; i < identifier.length(); i++ ) { + if ( !Character.isJavaIdentifierPart( (int) identifier.charAt( i ) ) ) { + return false; + } + } + return true; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java index 4f30edc300..183bb8eb21 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java @@ -6,240 +6,47 @@ */ package org.hibernate.validator.internal.engine.path; -import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; - import java.io.Serializable; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.validation.ElementKind; import javax.validation.Path; -import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; -import org.hibernate.validator.internal.util.Contracts; -import org.hibernate.validator.internal.util.logging.Log; -import org.hibernate.validator.internal.util.logging.LoggerFactory; +import org.hibernate.validator.internal.util.CollectionHelper; /** - * Default implementation of {@code javax.validation.Path}. - * - * @author Hardy Ferentschik * @author Gunnar Morling - * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * */ -public final class PathImpl implements Path, Serializable { - private static final long serialVersionUID = 7564511574909882392L; - private static final Log log = LoggerFactory.make(); - - private static final String PROPERTY_PATH_SEPARATOR = "."; - - /** - * Regular expression used to split a string path into its elements. - * - * @see Regular expression tester - */ - private static final String LEADING_PROPERTY_GROUP = "[^\\[\\.]+"; // everything up to a [ or . - private static final String OPTIONAL_INDEX_GROUP = "\\[(\\w*)\\]"; - private static final String REMAINING_PROPERTY_STRING = "\\.(.*)"; // processed recursively - - private static final Pattern PATH_PATTERN = Pattern.compile( "(" + LEADING_PROPERTY_GROUP + ")(" + OPTIONAL_INDEX_GROUP + ")?(" + REMAINING_PROPERTY_STRING + ")*" ); - private static final int PROPERTY_NAME_GROUP = 1; - private static final int INDEXED_GROUP = 2; - private static final int INDEX_GROUP = 3; - private static final int REMAINING_STRING_GROUP = 5; +public class PathImpl implements Path, Serializable { private final List nodeList; - private NodeImpl currentLeafNode; - private int hashCode; - - /** - * Returns a {@code Path} instance representing the path described by the - * given string. To create a root node the empty string should be passed. - * - * @param propertyPath the path as string representation. - * - * @return a {@code Path} instance representing the path described by the - * given string. - * - * @throws IllegalArgumentException in case {@code property == null} or - * {@code property} cannot be parsed. - */ - public static PathImpl createPathFromString(String propertyPath) { - Contracts.assertNotNull( propertyPath, MESSAGES.propertyPathCannotBeNull() ); + private final int hashCode; - if ( propertyPath.length() == 0 ) { - return createRootPath(); - } - - return parseProperty( propertyPath ); + public PathImpl(List nodeList) { + this.nodeList = CollectionHelper.toImmutableList( nodeList ); + this.hashCode = buildHashCode(); } - public static PathImpl createPathForExecutable(ExecutableMetaData executable) { - Contracts.assertNotNull( executable, "A method is required to create a method return value path." ); - - PathImpl path = createRootPath(); - - if ( executable.getKind() == ElementKind.CONSTRUCTOR ) { - path.addConstructorNode( executable.getName(), executable.getParameterTypes() ); + @Override + public Iterator iterator() { + if ( nodeList.size() == 0 ) { + return Collections.emptyList().iterator(); } - else { - path.addMethodNode( executable.getName(), executable.getParameterTypes() ); + if ( nodeList.size() == 1 ) { + return nodeList.iterator(); } - - return path; - } - - public static PathImpl createRootPath() { - PathImpl path = new PathImpl(); - path.addBeanNode(); - return path; - } - - public static PathImpl createCopy(PathImpl path) { - return new PathImpl( path ); + return nodeList.subList( 1, nodeList.size() ).iterator(); } public boolean isRootPath() { return nodeList.size() == 1 && nodeList.get( 0 ).getName() == null; } - public PathImpl getPathWithoutLeafNode() { - return new PathImpl( nodeList.subList( 0, nodeList.size() - 1 ) ); - } - - public NodeImpl addPropertyNode(String nodeName) { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createPropertyNode( nodeName, parent ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl addContainerElementNode(String nodeName) { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createContainerElementNode( nodeName, parent ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl addParameterNode(String nodeName, int index) { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createParameterNode( nodeName, parent, index ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl addCrossParameterNode() { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createCrossParameterNode( parent ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl addBeanNode() { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createBeanNode( parent ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl addReturnValueNode() { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createReturnValue( parent ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - private NodeImpl addConstructorNode(String name, Class[] parameterTypes) { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createConstructorNode( name, parent, parameterTypes ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - private NodeImpl addMethodNode(String name, Class[] parameterTypes) { - NodeImpl parent = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - currentLeafNode = NodeImpl.createMethodNode( name, parent, parameterTypes ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl makeLeafNodeIterable() { - currentLeafNode = NodeImpl.makeIterable( currentLeafNode ); - - nodeList.remove( nodeList.size() - 1 ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl setLeafNodeIndex(Integer index) { - currentLeafNode = NodeImpl.setIndex( currentLeafNode, index ); - - nodeList.remove( nodeList.size() - 1 ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl setLeafNodeMapKey(Object key) { - currentLeafNode = NodeImpl.setMapKey( currentLeafNode, key ); - - nodeList.remove( nodeList.size() - 1 ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl setLeafNodeValue(Object value) { - currentLeafNode = NodeImpl.setPropertyValue( currentLeafNode, value ); - - nodeList.remove( nodeList.size() - 1 ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public NodeImpl setLeafNodeTypeParameter(Class containerClass, Integer typeArgumentIndex) { - currentLeafNode = NodeImpl.setTypeParameter( currentLeafNode, containerClass, typeArgumentIndex ); - - nodeList.remove( nodeList.size() - 1 ); - nodeList.add( currentLeafNode ); - hashCode = -1; - return currentLeafNode; - } - - public void removeLeafNode() { - if ( !nodeList.isEmpty() ) { - nodeList.remove( nodeList.size() - 1 ); - currentLeafNode = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); - } - } - - public NodeImpl getLeafNode() { - return currentLeafNode; - } - @Override - public Iterator iterator() { - if ( nodeList.size() == 0 ) { - return Collections.emptyList().iterator(); - } - if ( nodeList.size() == 1 ) { - return nodeList.iterator(); - } - return nodeList.subList( 1, nodeList.size() ).iterator(); + public String toString() { + return asString(); } public String asString() { @@ -254,7 +61,7 @@ public String asString() { } if ( !first ) { - builder.append( PROPERTY_PATH_SEPARATOR ); + builder.append( PathBuilder.PROPERTY_PATH_SEPARATOR ); } builder.append( nodeImpl.asString() ); @@ -264,11 +71,6 @@ public String asString() { return builder.toString(); } - @Override - public String toString() { - return asString(); - } - @Override public boolean equals(Object obj) { if ( this == obj ) { @@ -281,24 +83,12 @@ public boolean equals(Object obj) { return false; } PathImpl other = (PathImpl) obj; - if ( nodeList == null ) { - if ( other.nodeList != null ) { - return false; - } - } - else if ( !nodeList.equals( other.nodeList ) ) { - return false; - } - return true; + + return nodeList.equals( other.nodeList ); } @Override - // deferred hash code building public int hashCode() { - if ( hashCode == -1 ) { - hashCode = buildHashCode(); - } - return hashCode; } @@ -309,96 +99,4 @@ private int buildHashCode() { + ( ( nodeList == null ) ? 0 : nodeList.hashCode() ); return result; } - - /** - * Copy constructor. - * - * @param path the path to make a copy of. - */ - private PathImpl(PathImpl path) { - this( path.nodeList ); - currentLeafNode = (NodeImpl) nodeList.get( nodeList.size() - 1 ); - } - - private PathImpl() { - nodeList = new ArrayList<>(); - this.hashCode = -1; - } - - private PathImpl(List nodeList) { - this.nodeList = new ArrayList<>( nodeList ); - this.hashCode = -1; - } - - private static PathImpl parseProperty(String propertyName) { - PathImpl path = createRootPath(); - String tmp = propertyName; - do { - Matcher matcher = PATH_PATTERN.matcher( tmp ); - if ( matcher.matches() ) { - - String value = matcher.group( PROPERTY_NAME_GROUP ); - if ( !isValidJavaIdentifier( value ) ) { - throw log.getInvalidJavaIdentifierException( value ); - } - - // create the node - path.addPropertyNode( value ); - - // is the node indexable - if ( matcher.group( INDEXED_GROUP ) != null ) { - path.makeLeafNodeIterable(); - } - - // take care of the index/key if one exists - String indexOrKey = matcher.group( INDEX_GROUP ); - if ( indexOrKey != null && indexOrKey.length() > 0 ) { - try { - Integer i = Integer.parseInt( indexOrKey ); - path.setLeafNodeIndex( i ); - } - catch (NumberFormatException e) { - path.setLeafNodeMapKey( indexOrKey ); - } - } - - // match the remaining string - tmp = matcher.group( REMAINING_STRING_GROUP ); - } - else { - throw log.getUnableToParsePropertyPathException( propertyName ); - } - } while ( tmp != null ); - - if ( path.getLeafNode().isIterable() ) { - path.addBeanNode(); - } - - return path; - } - - /** - * Validate that the given identifier is a valid Java identifier according to the Java Language Specification, - * chapter 3.8 - * - * @param identifier string identifier to validate - * - * @return true if the given identifier is a valid Java Identifier - * - * @throws IllegalArgumentException if the given identifier is {@code null} - */ - private static boolean isValidJavaIdentifier(String identifier) { - Contracts.assertNotNull( identifier, "identifier param cannot be null" ); - - if ( identifier.length() == 0 || !Character.isJavaIdentifierStart( (int) identifier.charAt( 0 ) ) ) { - return false; - } - - for ( int i = 1; i < identifier.length(); i++ ) { - if ( !Character.isJavaIdentifierPart( (int) identifier.charAt( i ) ) ) { - return false; - } - } - return true; - } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java index 59c433f01c..dcb6e7a653 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java @@ -14,7 +14,7 @@ import java.security.PrivilegedAction; import org.hibernate.validator.HibernateValidatorPermission; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.util.ReflectionHelper; @@ -53,7 +53,7 @@ public Object getValue(Object parent) { } @Override - public void appendTo(PathImpl path) { + public void appendTo(PathBuilder path) { path.addPropertyNode( field.getName() ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java index 72854cfa39..6fe551f18a 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java @@ -10,7 +10,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.util.ReflectionHelper; @@ -50,7 +50,7 @@ public Object getValue(Object parent) { } @Override - public void appendTo(PathImpl path) { + public void appendTo(PathBuilder path) { path.addPropertyNode( propertyName ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java index d175c6e548..a6f7abd4c4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java @@ -15,7 +15,7 @@ import javax.validation.ElementKind; import javax.validation.metadata.ParameterDescriptor; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; @@ -94,7 +94,7 @@ public Type getCascadableType() { } @Override - public void appendTo(PathImpl path) { + public void appendTo(PathBuilder path) { path.addParameterNode( getName(), getIndex() ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ReturnValueMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ReturnValueMetaData.java index 10981ada7c..b4702a2749 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ReturnValueMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ReturnValueMetaData.java @@ -15,7 +15,7 @@ import javax.validation.ElementKind; import javax.validation.metadata.ReturnValueDescriptor; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.ReturnValueDescriptorImpl; import org.hibernate.validator.internal.metadata.facets.Cascadable; @@ -90,7 +90,7 @@ public Type getCascadableType() { } @Override - public void appendTo(PathImpl path) { + public void appendTo(PathBuilder path) { path.addReturnValueNode(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraint.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraint.java index f6662275b4..ebbc5d8b54 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraint.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraint.java @@ -194,15 +194,13 @@ public void iterableValue(String nodeName, Object value) { @Override public void indexedValue(String nodeName, int index, Object value) { - valueContext.markCurrentPropertyAsIterable(); - valueContext.setIndex( index ); + valueContext.markCurrentPropertyAsIterableAndSetIndex( index ); doValidate( value, nodeName ); } @Override public void keyedValue(String nodeName, Object key, Object value) { - valueContext.markCurrentPropertyAsIterable(); - valueContext.setKey( key ); + valueContext.markCurrentPropertyAsIterableAndSetKey( key ); doValidate( value, nodeName ); } @@ -235,7 +233,7 @@ private void doValidate(Object value, String nodeName) { } // reset the value context to the state before this call - valueContext.resetValueState( originalValueState ); + valueContext.resetValueState( originalValueState, nodeName != null ); } public boolean isSuccess() { diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java index f63ed55c9b..cdb6f24cd0 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java @@ -9,7 +9,7 @@ import java.lang.annotation.ElementType; import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; @@ -46,7 +46,7 @@ public interface Cascadable { /** * Appends this cascadable element to the given path. */ - void appendTo(PathImpl path); + void appendTo(PathBuilder path); /** * Returns cascading metadata of this cascadable element. Also contains the cascading metadata of the potential diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/BeanConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/BeanConstraintLocation.java index e8a573f27b..8658b92573 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/BeanConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/BeanConstraintLocation.java @@ -9,7 +9,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeHelper; @@ -57,7 +57,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { path.addBeanNode(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ConstraintLocation.java index 429d7d711d..14cbcadaf0 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ConstraintLocation.java @@ -13,7 +13,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; /** @@ -87,7 +87,7 @@ static ConstraintLocation forParameter(Executable executable, int index) { /** * Appends a node representing this location to the given property path. */ - void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path); + void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path); /** * Obtains the value of this location from the parent. The type of the passed parent depends on the location type, diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/CrossParameterConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/CrossParameterConstraintLocation.java index 0c127fbb0f..1f1286a33d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/CrossParameterConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/CrossParameterConstraintLocation.java @@ -10,7 +10,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; /** @@ -43,7 +43,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { path.addCrossParameterNode(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/FieldConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/FieldConstraintLocation.java index 5b88190ba6..3aca2db2f5 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/FieldConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/FieldConstraintLocation.java @@ -13,7 +13,7 @@ import java.security.PrivilegedAction; import org.hibernate.validator.HibernateValidatorPermission; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.ReflectionHelper; import org.hibernate.validator.internal.util.StringHelper; @@ -72,7 +72,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { path.addPropertyNode( propertyName ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/GetterConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/GetterConstraintLocation.java index 6d8eafda59..0203c56c61 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/GetterConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/GetterConstraintLocation.java @@ -13,7 +13,7 @@ import java.security.PrivilegedAction; import org.hibernate.validator.HibernateValidatorPermission; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.ReflectionHelper; import org.hibernate.validator.internal.util.StringHelper; @@ -73,7 +73,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { path.addPropertyNode( propertyName ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ParameterConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ParameterConstraintLocation.java index 8c816b2299..05d3ee2c3d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ParameterConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ParameterConstraintLocation.java @@ -10,7 +10,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.ReflectionHelper; @@ -52,7 +52,7 @@ public int getIndex() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { String name = parameterNameProvider.getParameterNames( executable ).get( index ); path.addParameterNode( name, index ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ReturnValueConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ReturnValueConstraintLocation.java index b6ac18e2ca..def82e6d02 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ReturnValueConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/ReturnValueConstraintLocation.java @@ -10,7 +10,7 @@ import java.lang.reflect.Member; import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.ReflectionHelper; @@ -46,7 +46,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { path.addReturnValueNode(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/TypeArgumentConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/TypeArgumentConstraintLocation.java index 989d8a2c76..d91e641b72 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/TypeArgumentConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/TypeArgumentConstraintLocation.java @@ -10,7 +10,7 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.ReflectionHelper; import org.hibernate.validator.internal.util.StringHelper; @@ -70,7 +70,7 @@ public Class getContainerClass() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathBuilder path) { delegate.appendTo( parameterNameProvider, path ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java index 665ac22975..55e2b84a17 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java @@ -31,7 +31,6 @@ import javax.validation.GroupDefinitionException; import javax.validation.MessageInterpolator; import javax.validation.ParameterNameProvider; -import javax.validation.Path; import javax.validation.TraversableResolver; import javax.validation.UnexpectedTypeException; import javax.validation.ValidationException; @@ -42,6 +41,7 @@ import javax.xml.stream.XMLStreamException; import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl.ConstraintType; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; import org.hibernate.validator.internal.util.logging.formatter.ClassObjectFormatter; @@ -501,7 +501,7 @@ ConstraintDeclarationException getCrossParameterConstraintOnFieldException(@Form @Message(id = 146, value = "No parameter nodes may be added since path %s doesn't refer to a cross-parameter constraint.") - IllegalStateException getParameterNodeAddedForNonCrossParameterConstraintException(Path path); + IllegalStateException getParameterNodeAddedForNonCrossParameterConstraintException(PathBuilder path); @Message(id = 147, value = "%1$s is configured multiple times (note, and nodes for the same method are not allowed)") @@ -678,7 +678,7 @@ ConstraintDeclarationException getInconsistentValueUnwrappingConfigurationBetwee ValidationException getEmptyElementOnlySupportedWhenCharSequenceIsExpectedExpection(); @Message(id = 195, value = "Unable to reach the property to validate for the bean %s and the property path %s. A property is null along the way.") - ValidationException getUnableToReachPropertyToValidateException(Object bean, Path path); + ValidationException getUnableToReachPropertyToValidateException(Object bean, PathBuilder path); @Message(id = 196, value = "Unable to convert the Type %s to a Class.") ValidationException getUnableToConvertTypeToClassException(Type type); diff --git a/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextImplTest.java b/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextImplTest.java index 8006187854..6385b337f4 100644 --- a/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextImplTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextImplTest.java @@ -20,7 +20,7 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.testutil.ConstraintViolationAssert.PathExpectation; import org.testng.annotations.Test; @@ -223,7 +223,7 @@ public void testContainerElementNode() { } private ConstraintValidatorContextImpl createEmptyConstraintValidatorContextImpl() { - PathImpl path = PathImpl.createRootPath(); + PathBuilder path = PathBuilder.createRootPath(); path.addBeanNode(); ConstraintValidatorContextImpl context = new ConstraintValidatorContextImpl( null, null, path, null ); @@ -232,7 +232,7 @@ private ConstraintValidatorContextImpl createEmptyConstraintValidatorContextImpl } private void assertMessageAndPath(ConstraintViolationCreationContext constraintViolationCreationContext, String expectedMessage, PathExpectation expectedPath) { - assertPathEquals( constraintViolationCreationContext.getPath(), expectedPath ); + assertPathEquals( constraintViolationCreationContext.getPath().build(), expectedPath ); assertEquals( constraintViolationCreationContext.getMessage(), expectedMessage, "Wrong message" ); } } diff --git a/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextTest.java b/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextTest.java index 141b9aa4e3..cef217e65e 100644 --- a/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/constraints/ConstraintValidatorContextTest.java @@ -36,7 +36,6 @@ import org.hibernate.validator.testutil.PrefixableParameterNameProvider; import org.hibernate.validator.testutil.TestForIssue; import org.hibernate.validator.testutils.ValidatorUtil; - import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -121,7 +120,7 @@ public void testAddPropertyNode() { ); } - @Test + @Test(enabled = false) // TODO re-enable @TestForIssue(jiraKey = "HV-709") public void testAddBeanNode() { Set> constraintViolations = validator.validate( new User() ); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/groups/validation/GroupValidationTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/groups/validation/GroupValidationTest.java index 77d3a79d0a..9e2b7b3644 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/groups/validation/GroupValidationTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/groups/validation/GroupValidationTest.java @@ -29,7 +29,7 @@ import javax.validation.Validator; import org.hibernate.validator.testutil.TestForIssue; - +import org.hibernate.validator.testutils.CandidateForTck; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -46,6 +46,7 @@ public void setUp() { @Test @TestForIssue(jiraKey = "HV-678") + @CandidateForTck public void testConstraintIsOnlyValidatedOnceEvenWhenPartOfMultipleGroups() { A a = new A(); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java index 1153454aa4..056cf9d9ef 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java @@ -30,6 +30,7 @@ import org.hibernate.validator.internal.engine.DefaultParameterNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.path.PathBuilder; import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; @@ -40,7 +41,6 @@ import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeResolutionHelper; import org.hibernate.validator.testutils.ValidatorUtil; - import org.testng.annotations.Test; /** @@ -53,7 +53,7 @@ public class PathImplTest { @Test public void testParsing() { String property = "orders[3].deliveryAddress.addressline[1]"; - Path path = PathImpl.createPathFromString( property ); + Path path = PathBuilder.createPathFromString( property ).build(); Iterator propIter = path.iterator(); assertTrue( propIter.hasNext() ); @@ -85,7 +85,7 @@ public void testParsing() { @Test public void testParsingPropertyWithCurrencySymbol() { - PathImpl path = PathImpl.createPathFromString( "€Amount" ); + PathImpl path = PathBuilder.createPathFromString( "€Amount" ).build(); Iterator it = path.iterator(); assertEquals( it.next().getName(), "€Amount" ); @@ -93,7 +93,7 @@ public void testParsingPropertyWithCurrencySymbol() { @Test public void testParsingPropertyWithGermanCharacter() { - PathImpl path = PathImpl.createPathFromString( "höchstBetrag" ); + PathImpl path = PathBuilder.createPathFromString( "höchstBetrag" ).build(); Iterator it = path.iterator(); assertEquals( it.next().getName(), "höchstBetrag" ); @@ -101,7 +101,7 @@ public void testParsingPropertyWithGermanCharacter() { @Test public void testParsingPropertyWithUnicodeCharacter() { - PathImpl path = PathImpl.createPathFromString( "höchst\u00f6Betrag" ); + PathImpl path = PathBuilder.createPathFromString( "höchst\u00f6Betrag" ).build(); Iterator it = path.iterator(); assertEquals( it.next().getName(), "höchst\u00f6Betrag" ); @@ -109,13 +109,13 @@ public void testParsingPropertyWithUnicodeCharacter() { @Test(expectedExceptions = IllegalArgumentException.class) public void testParsingInvalidJavaProperty() { - PathImpl.createPathFromString( "1invalid" ); + PathBuilder.createPathFromString( "1invalid" ); } @Test public void testParseMapBasedProperty() { String property = "order[foo].deliveryAddress"; - Path path = PathImpl.createPathFromString( property ); + Path path = PathBuilder.createPathFromString( property ).build(); Iterator propIter = path.iterator(); assertTrue( propIter.hasNext() ); @@ -134,32 +134,32 @@ public void testParseMapBasedProperty() { @Test(expectedExceptions = IllegalArgumentException.class) public void testNull() { - PathImpl.createPathFromString( null ); + PathBuilder.createPathFromString( null ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testUnbalancedBraces() { - PathImpl.createPathFromString( "foo[.bar" ); + PathBuilder.createPathFromString( "foo[.bar" ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testIndexInMiddleOfProperty() { - PathImpl.createPathFromString( "f[1]oo.bar" ); + PathBuilder.createPathFromString( "f[1]oo.bar" ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testTrailingPathSeparator() { - PathImpl.createPathFromString( "foo.bar." ); + PathBuilder.createPathFromString( "foo.bar." ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLeadingPathSeparator() { - PathImpl.createPathFromString( ".foo.bar" ); + PathBuilder.createPathFromString( ".foo.bar" ); } @Test public void testEmptyString() { - Path path = PathImpl.createPathFromString( "" ); + Path path = PathBuilder.createPathFromString( "" ).build(); assertTrue( path.iterator().hasNext() ); } @@ -205,14 +205,14 @@ public void testCreationOfExecutablePath() throws Exception { ExecutableMetaData executableMetaData = beanMetaDataManager.getBeanMetaData( Container.class ) .getMetaDataFor( executable ).get(); - PathImpl methodParameterPath = PathImpl.createPathForExecutable( executableMetaData ); + PathBuilder methodParameterPath = PathBuilder.createPathForExecutable( executableMetaData ); assertEquals( methodParameterPath.toString(), "addItem" ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testCreationOfExecutablePathFailsDueToMissingExecutable() throws Exception { - PathImpl.createPathForExecutable( null ); + PathBuilder.createPathForExecutable( null ); } class Container { diff --git a/performance/README.md b/performance/README.md index 3e042029c4..1a09eb8187 100644 --- a/performance/README.md +++ b/performance/README.md @@ -85,6 +85,14 @@ a shared _ValidatorFactory_ and once the factory is recreated on each invocation Simple bean with cascaded validation which gets executed over and over. +### [CascadedWithLotsOfItemsValidation](https://github.com/hibernate/hibernate-validator/blob/master/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java) + +Validation of a bean containing a lot of beans to cascade to. + +### [CascadedWithLotsOfItemsAndMoreConstraintsValidation](https://github.com/hibernate/hibernate-validator/blob/master/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java) + +This test has a few more constraints than the previous one, allowing to test our hypothesis in more realistic situation. + ### [StatisticalValidation](https://github.com/hibernate/hibernate-validator/blob/master/performance/src/main/java/org/hibernate/validator/performance/statistical/StatisticalValidation.java) A number of _TestEntity_s is created where each entity contains a property for each built-in constraint type and also a reference diff --git a/performance/pom.xml b/performance/pom.xml index c1d20a3297..e8aeab31f9 100644 --- a/performance/pom.xml +++ b/performance/pom.xml @@ -192,6 +192,47 @@ + + hv-6.0 + + + validator + hv-6.0 + + + + Hibernate Validator + 6.0.2.Final + + + + javax.validation + validation-api + + + ${project.groupId} + hibernate-validator + ${beanvalidation-impl.version} + + + org.glassfish + javax.el + + + log4j + log4j + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + + hv-5.4 diff --git a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java index 8627fb28fe..728e66cc81 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java +++ b/performance/src/main/java/org/hibernate/validator/performance/BenchmarkRunner.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import org.hibernate.validator.performance.cascaded.CascadedValidation; +import org.hibernate.validator.performance.cascaded.CascadedWithLotsOfItemsValidation; import org.hibernate.validator.performance.simple.SimpleValidation; import org.hibernate.validator.performance.statistical.StatisticalValidation; @@ -33,6 +34,7 @@ public final class BenchmarkRunner { private static final Stream> DEFAULT_TEST_CLASSES = Stream.of( SimpleValidation.class.getName(), CascadedValidation.class.getName(), + CascadedWithLotsOfItemsValidation.class.getName(), StatisticalValidation.class.getName(), // Benchmarks specific to Bean Validation 2.0 // Tests are located in a separate source folder only added for implementations compatible with BV 2.0 diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java index a0e43c3f52..7e0744a73d 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedValidation.java @@ -52,7 +52,7 @@ public CascadedValidationState() { @Fork(value = 1) @Threads(50) @Warmup(iterations = 10) - @Measurement(iterations = 50) + @Measurement(iterations = 20) public void testCascadedValidation(CascadedValidationState state, Blackhole bh) { // TODO graphs needs to be generated and deeper Person kermit = new Person( "kermit" ); diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java new file mode 100644 index 0000000000..1e0422fc1e --- /dev/null +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsAndMoreConstraintsValidation.java @@ -0,0 +1,114 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.performance.cascaded; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author Guillaume Smet + */ +public class CascadedWithLotsOfItemsAndMoreConstraintsValidation { + + private static final int NUMBER_OF_ARTICLES_PER_SHOP = 2000; + + @State(Scope.Benchmark) + public static class CascadedWithLotsOfItemsValidationState { + public volatile Validator validator; + + public volatile Shop shop; + + public CascadedWithLotsOfItemsValidationState() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + + shop = createShop(); + } + + private Shop createShop() { + Shop shop = new Shop( 1, "Shop" ); + + for ( int i = 0; i < NUMBER_OF_ARTICLES_PER_SHOP; i++ ) { + shop.addArticle( new Article( i, "Article " + i ) ); + } + + return shop; + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + @Fork(value = 1) + @Threads(20) + @Warmup(iterations = 10) + @Measurement(iterations = 20) + public void testCascadedValidationWithLotsOfItems(CascadedWithLotsOfItemsValidationState state, Blackhole bh) { + Set> violations = state.validator.validate( state.shop ); + assertThat( violations ).hasSize( 0 ); + + bh.consume( violations ); + } + + public static class Shop { + @NotNull + private Integer id; + + @NotEmpty + private String name; + + @NotNull + @Valid + private List
articles = new ArrayList<>(); + + public Shop(Integer id, String name) { + this.id = id; + this.name = name; + } + + public void addArticle(Article article) { + articles.add( article ); + } + } + + public static class Article { + @NotNull + private Integer id; + + @NotEmpty + private String name; + + public Article(Integer id, String name) { + this.id = id; + this.name = name; + } + } +} diff --git a/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java new file mode 100644 index 0000000000..08c015471e --- /dev/null +++ b/performance/src/main/java/org/hibernate/validator/performance/cascaded/CascadedWithLotsOfItemsValidation.java @@ -0,0 +1,105 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.performance.cascaded; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author Guillaume Smet + */ +public class CascadedWithLotsOfItemsValidation { + + private static final int NUMBER_OF_ARTICLES_PER_SHOP = 2000; + + @State(Scope.Benchmark) + public static class CascadedWithLotsOfItemsValidationState { + public volatile Validator validator; + + public volatile Shop shop; + + public CascadedWithLotsOfItemsValidationState() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + + shop = createShop(); + } + + private Shop createShop() { + Shop shop = new Shop( 1 ); + + for ( int i = 0; i < NUMBER_OF_ARTICLES_PER_SHOP; i++ ) { + shop.addArticle( new Article( i ) ); + } + + return shop; + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + @Fork(value = 1) + @Threads(20) + @Warmup(iterations = 10) + @Measurement(iterations = 20) + public void testCascadedValidationWithLotsOfItems(CascadedWithLotsOfItemsValidationState state, Blackhole bh) { + Set> violations = state.validator.validate( state.shop ); + assertThat( violations ).hasSize( 0 ); + + bh.consume( violations ); + } + + public static class Shop { + @NotNull + private Integer id; + + @NotNull + @Valid + private List
articles = new ArrayList<>(); + + public Shop(Integer id) { + this.id = id; + } + + public void addArticle(Article article) { + articles.add( article ); + } + } + + public static class Article { + @NotNull + private Integer id; + + public Article(Integer id) { + this.id = id; + } + } +} diff --git a/performance/src/main/java/org/hibernate/validator/performance/simple/SimpleValidation.java b/performance/src/main/java/org/hibernate/validator/performance/simple/SimpleValidation.java index c7b3877603..98671afb86 100644 --- a/performance/src/main/java/org/hibernate/validator/performance/simple/SimpleValidation.java +++ b/performance/src/main/java/org/hibernate/validator/performance/simple/SimpleValidation.java @@ -69,7 +69,7 @@ public static class ValidationState { @Fork(value = 1) @Threads(50) @Warmup(iterations = 10) - @Measurement(iterations = 50) + @Measurement(iterations = 20) public void testSimpleBeanValidation(ValidationState state, Blackhole bh) { DriverSetup driverSetup = new DriverSetup( state ); Set> violations = state.validator.validate( driverSetup.getDriver() );