diff --git a/cdi/src/main/java/org/hibernate/validator/cdi/interceptor/spi/ValidationInterceptor.java b/cdi/src/main/java/org/hibernate/validator/cdi/interceptor/spi/ValidationInterceptor.java index 4e0ef983be..091779e415 100644 --- a/cdi/src/main/java/org/hibernate/validator/cdi/interceptor/spi/ValidationInterceptor.java +++ b/cdi/src/main/java/org/hibernate/validator/cdi/interceptor/spi/ValidationInterceptor.java @@ -4,6 +4,7 @@ */ package org.hibernate.validator.cdi.interceptor.spi; +import java.io.Serial; import java.io.Serializable; import java.lang.reflect.Member; import java.util.Arrays; @@ -37,6 +38,7 @@ @Priority(Interceptor.Priority.PLATFORM_AFTER + 800) public class ValidationInterceptor implements Serializable { + @Serial private static final long serialVersionUID = 604440259030722151L; /** diff --git a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java index 7f55836bb1..83b2ac99de 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java +++ b/engine/src/main/java/org/hibernate/validator/internal/constraintvalidators/hv/UniqueElementsValidator.java @@ -7,6 +7,7 @@ import static java.util.stream.Collectors.toList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -53,7 +54,10 @@ public boolean isValid(Collection collection, ConstraintValidatorContext constra if ( constraintValidatorContext instanceof HibernateConstraintValidatorContext ) { constraintValidatorContext.unwrap( HibernateConstraintValidatorContext.class ) .addMessageParameter( "duplicates", duplicates.stream().map( String::valueOf ).collect( Collectors.joining( ", " ) ) ) - .withDynamicPayload( CollectionHelper.toImmutableList( duplicates ) ); + // We cannot leverage the CollectionHelper.toImmutableList here as it does not allow `null` values. + // User collections may have `null`s in it and those could as well be duplicates + // so let's rely on the Collections.unmodifiableList here which accepts `null` values as long as the underlying collection allows it: + .withDynamicPayload( Collections.unmodifiableList( duplicates ) ); } return false; diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ConstraintViolationImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ConstraintViolationImpl.java index 8b7208e13d..32106c772d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ConstraintViolationImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ConstraintViolationImpl.java @@ -4,6 +4,7 @@ */ package org.hibernate.validator.internal.engine; +import java.io.Serial; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.util.Map; @@ -24,6 +25,7 @@ public class ConstraintViolationImpl implements HibernateConstraintViolation, Serializable { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + @Serial private static final long serialVersionUID = -4970067626703103139L; private final String interpolatedMessage; 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 a611d5a2fa..329ccc2262 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 @@ -38,8 +38,8 @@ import org.hibernate.validator.internal.engine.groups.Sequence; import org.hibernate.validator.internal.engine.groups.ValidationOrder; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.engine.path.NodeImpl; -import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.engine.resolver.TraversableResolvers; import org.hibernate.validator.internal.engine.validationcontext.BaseBeanValidationContext; import org.hibernate.validator.internal.engine.validationcontext.ExecutableValidationContext; @@ -164,7 +164,7 @@ public final Set> validate(T object, Class... grou validatorScopedContext.getParameterNameProvider(), object, validationContext.getRootBeanMetaData(), - PathImpl.createRootPath() + ModifiablePath.createRootPath() ); return validateInContext( validationContext, valueContext, validationOrder ); @@ -184,7 +184,7 @@ public final Set> validateProperty(T object, String p return Collections.emptySet(); } - PathImpl propertyPath = PathImpl.createPathFromString( propertyName ); + ModifiablePath propertyPath = ModifiablePath.createPathFromString( propertyName ); BaseBeanValidationContext validationContext = getValidationContextBuilder().forValidateProperty( rootBeanClass, rootBeanMetaData, object, propertyPath ); @@ -211,7 +211,7 @@ public final Set> validateValue(Class beanType, St return Collections.emptySet(); } - PathImpl propertyPath = PathImpl.createPathFromString( propertyName ); + ModifiablePath propertyPath = ModifiablePath.createPathFromString( propertyName ); BaseBeanValidationContext validationContext = getValidationContextBuilder().forValidateValue( beanType, rootBeanMetaData, propertyPath ); ValidationOrder validationOrder = determineGroupValidationOrder( groups ); @@ -813,7 +813,7 @@ private BeanValueContext buildNewLocalExecutionContext(ValueContext Set> validateValueInContext(BaseBeanValidationContext validationContext, Object value, PathImpl propertyPath, + private Set> validateValueInContext(BaseBeanValidationContext validationContext, Object value, ModifiablePath propertyPath, ValidationOrder validationOrder) { BeanValueContext valueContext = getValueContextForValueValidation( validationContext.getRootBeanClass(), propertyPath ); valueContext.setCurrentValidatedValue( value ); @@ -902,7 +902,7 @@ private void validateParametersInContext(ExecutableValidationContext vali validatorScopedContext.getParameterNameProvider(), parameterValues, executableMetaData.getValidatableParametersMetaData(), - PathImpl.createPathForExecutable( executableMetaData ) + ModifiablePath.createPathForExecutable( executableMetaData ) ); groupIterator = validationOrder.getGroupIterator(); @@ -1035,7 +1035,7 @@ private ValueContext getExecutableValueContext(T object, Executab validatorScopedContext.getParameterNameProvider(), object, validatable, - PathImpl.createPathForExecutable( executableMetaData ) + ModifiablePath.createPathForExecutable( executableMetaData ) ); valueContext.setCurrentGroup( group ); @@ -1078,7 +1078,7 @@ private void validateReturnValueInContext(ExecutableValidationContext validatorScopedContext.getParameterNameProvider(), value, executableMetaData.getReturnValueMetaData(), - PathImpl.createPathForExecutable( executableMetaData ) + ModifiablePath.createPathForExecutable( executableMetaData ) ); groupIterator = validationOrder.getGroupIterator(); @@ -1184,7 +1184,7 @@ private void validateReturnValueForSingleGroup(BaseBeanValidationContext * @return Returns an instance of {@code ValueContext} which describes the local validation context associated to * the given property path. */ - private BeanValueContext getValueContextForPropertyValidation(BaseBeanValidationContext validationContext, PathImpl propertyPath) { + private BeanValueContext getValueContextForPropertyValidation(BaseBeanValidationContext validationContext, ModifiablePath propertyPath) { Class clazz = validationContext.getRootBeanClass(); BeanMetaData beanMetaData = validationContext.getRootBeanMetaData(); Object value = validationContext.getRootBean(); @@ -1262,7 +1262,7 @@ else if ( propertyPathNode.getKey() != null ) { * the given property path. */ private BeanValueContext getValueContextForValueValidation(Class rootBeanClass, - PathImpl propertyPath) { + ModifiablePath propertyPath) { Class clazz = rootBeanClass; BeanMetaData beanMetaData = null; PropertyMetaData propertyMetaData = null; @@ -1330,13 +1330,13 @@ private boolean isValidationRequired(BaseBeanValidationContext validationCont ); } - private boolean isReachable(BaseBeanValidationContext validationContext, Object traversableObject, PathImpl path, + private boolean isReachable(BaseBeanValidationContext validationContext, Object traversableObject, ModifiablePath path, ConstraintLocationKind constraintLocationKind) { if ( needToCallTraversableResolver( path, constraintLocationKind ) ) { return true; } - Path pathToObject = PathImpl.createCopyWithoutLeafNode( path ); + Path pathToObject = ModifiablePath.createCopyWithoutLeafNode( path ); try { return validationContext.getTraversableResolver().isReachable( traversableObject, @@ -1351,7 +1351,7 @@ private boolean isReachable(BaseBeanValidationContext validationContext, Obje } } - private boolean needToCallTraversableResolver(PathImpl path, ConstraintLocationKind constraintLocationKind) { + private boolean needToCallTraversableResolver(ModifiablePath path, ConstraintLocationKind constraintLocationKind) { // 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. @@ -1362,7 +1362,7 @@ private boolean needToCallTraversableResolver(PathImpl path, ConstraintLocationK || isReturnValueValidation( path ); } - private boolean isCascadeRequired(BaseBeanValidationContext validationContext, Object traversableObject, PathImpl path, + private boolean isCascadeRequired(BaseBeanValidationContext validationContext, Object traversableObject, ModifiablePath path, ConstraintLocationKind constraintLocationKind) { if ( needToCallTraversableResolver( path, constraintLocationKind ) ) { return true; @@ -1373,7 +1373,7 @@ private boolean isCascadeRequired(BaseBeanValidationContext validationContext return false; } - Path pathToObject = PathImpl.createCopyWithoutLeafNode( path ); + Path pathToObject = ModifiablePath.createCopyWithoutLeafNode( path ); try { return validationContext.getTraversableResolver().isCascadable( traversableObject, @@ -1392,15 +1392,15 @@ private boolean isClassLevelConstraint(ConstraintLocationKind constraintLocation return ConstraintLocationKind.TYPE.equals( constraintLocationKind ); } - private boolean isCrossParameterValidation(PathImpl path) { + private boolean isCrossParameterValidation(ModifiablePath path) { return path.getLeafNode().getKind() == ElementKind.CROSS_PARAMETER; } - private boolean isParameterValidation(PathImpl path) { + private boolean isParameterValidation(ModifiablePath path) { return path.getLeafNode().getKind() == ElementKind.PARAMETER; } - private boolean isReturnValueValidation(PathImpl path) { + private boolean isReturnValueValidation(ModifiablePath path) { return path.getLeafNode().getKind() == ElementKind.RETURN_VALUE; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ComposingConstraintTree.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ComposingConstraintTree.java index 17d18d7ec6..3558504818 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ComposingConstraintTree.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ComposingConstraintTree.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import jakarta.validation.ConstraintValidator; @@ -38,7 +37,7 @@ * @author Guillaume Smet * @author Marko Bekhta */ -class ComposingConstraintTree extends ConstraintTree { +final class ComposingConstraintTree extends ConstraintTree { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); @@ -61,6 +60,23 @@ private ConstraintTree createConstraintTree(Constraint } } + @Override + public boolean validateConstraints(ValidationContext validationContext, ValueContext valueContext) { + List violatedConstraintValidatorContexts = new ArrayList<>( 5 ); + validateConstraints( validationContext, valueContext, violatedConstraintValidatorContexts ); + if ( !violatedConstraintValidatorContexts.isEmpty() ) { + for ( ConstraintValidatorContextImpl constraintValidatorContext : violatedConstraintValidatorContexts ) { + for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) { + validationContext.addConstraintFailure( + valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor() + ); + } + } + return false; + } + return true; + } + @Override protected void validateConstraints(ValidationContext validationContext, ValueContext valueContext, @@ -69,7 +85,7 @@ protected void validateConstraints(ValidationContext validationContext, validationContext, valueContext, violatedConstraintValidatorContexts ); - Optional violatedLocalConstraintValidatorContext; + ConstraintValidatorContextImpl violatedLocalConstraintValidatorContext; // After all children are validated the actual ConstraintValidator of the constraint itself is executed if ( mainConstraintNeedsEvaluation( validationContext, violatedConstraintValidatorContexts ) ) { @@ -103,7 +119,7 @@ protected void validateConstraints(ValidationContext validationContext, // We re-evaluate the boolean composition by taking into consideration also the violations // from the local constraintValidator - if ( !violatedLocalConstraintValidatorContext.isPresent() ) { + if ( violatedLocalConstraintValidatorContext == null ) { compositionResult.setAtLeastOneTrue( true ); } else { @@ -111,7 +127,7 @@ protected void validateConstraints(ValidationContext validationContext, } } else { - violatedLocalConstraintValidatorContext = Optional.empty(); + violatedLocalConstraintValidatorContext = null; } if ( !passesCompositionTypeRequirement( violatedConstraintValidatorContexts, compositionResult ) ) { @@ -157,7 +173,7 @@ private boolean mainConstraintNeedsEvaluation(ValidationContext validationCon private void prepareFinalConstraintViolations(ValidationContext validationContext, ValueContext valueContext, Collection violatedConstraintValidatorContexts, - Optional localConstraintValidatorContext) { + ConstraintValidatorContextImpl localConstraintValidatorContext) { if ( reportAsSingleViolation() ) { // We clear the current violations list anyway violatedConstraintValidatorContexts.clear(); @@ -166,7 +182,7 @@ private void prepareFinalConstraintViolations(ValidationContext validationCon // violations or not (or if there is no local ConstraintValidator at all). // If not we create a violation // using the error message in the annotation declaration at top level. - if ( !localConstraintValidatorContext.isPresent() ) { + if ( localConstraintValidatorContext == null ) { violatedConstraintValidatorContexts.add( validationContext.createConstraintValidatorContextFor( descriptor, valueContext.getPropertyPath() @@ -183,8 +199,8 @@ private void prepareFinalConstraintViolations(ValidationContext validationCon // as checked in test CustomErrorMessage.java // If no violations have been reported from the local ConstraintValidator, or no such validator exists, // then we just add an empty list. - if ( localConstraintValidatorContext.isPresent() ) { - violatedConstraintValidatorContexts.add( localConstraintValidatorContext.get() ); + if ( localConstraintValidatorContext != null ) { + violatedConstraintValidatorContexts.add( localConstraintValidatorContext ); } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java index 393c51ed05..4ad62ee8bb 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java @@ -7,9 +7,7 @@ import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandles; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.Optional; import jakarta.validation.ConstraintDeclarationException; @@ -33,7 +31,7 @@ * @author Guillaume Smet * @author Marko Bekhta */ -public abstract class ConstraintTree { +public abstract sealed class ConstraintTree permits SimpleConstraintTree, ComposingConstraintTree { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); @@ -68,21 +66,7 @@ public static ConstraintTree of(ConstraintValidatorMan } } - public final boolean validateConstraints(ValidationContext validationContext, ValueContext valueContext) { - List violatedConstraintValidatorContexts = new ArrayList<>( 5 ); - validateConstraints( validationContext, valueContext, violatedConstraintValidatorContexts ); - if ( !violatedConstraintValidatorContexts.isEmpty() ) { - for ( ConstraintValidatorContextImpl constraintValidatorContext : violatedConstraintValidatorContexts ) { - for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) { - validationContext.addConstraintFailure( - valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor() - ); - } - } - return false; - } - return true; - } + public abstract boolean validateConstraints(ValidationContext validationContext, ValueContext valueContext); protected abstract void validateConstraints(ValidationContext validationContext, ValueContext valueContext, Collection violatedConstraintValidatorContexts); @@ -168,7 +152,7 @@ private ValidationException getExceptionForNullValidator(Type validatedValueType * @return an {@link Optional#empty()} if there is no violation or a corresponding {@link ConstraintValidatorContextImpl} * otherwise. */ - protected final Optional validateSingleConstraint( + protected final ConstraintValidatorContextImpl validateSingleConstraint( ValueContext valueContext, ConstraintValidatorContextImpl constraintValidatorContext, ConstraintValidator validator) { @@ -187,9 +171,9 @@ protected final Optional validateSingleConst if ( !isValid ) { //We do not add these violations yet, since we don't know how they are //going to influence the final boolean evaluation - return Optional.of( constraintValidatorContext ); + return constraintValidatorContext; } - return Optional.empty(); + return null; } @Override 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 86e7802c32..33db80862f 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 @@ -28,7 +28,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; import org.hibernate.validator.constraintvalidation.HibernateConstraintViolationBuilder; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.logging.Log; @@ -49,7 +49,7 @@ public class ConstraintValidatorContextImpl implements HibernateConstraintValida private final ClockProvider clockProvider; private final ExpressionLanguageFeatureLevel defaultConstraintExpressionLanguageFeatureLevel; private final ExpressionLanguageFeatureLevel defaultCustomViolationExpressionLanguageFeatureLevel; - private final PathImpl basePath; + private final ModifiablePath basePath; private final ConstraintDescriptor constraintDescriptor; private List constraintViolationCreationContexts; private boolean defaultDisabled; @@ -58,7 +58,7 @@ public class ConstraintValidatorContextImpl implements HibernateConstraintValida public ConstraintValidatorContextImpl( ClockProvider clockProvider, - PathImpl propertyPath, + ModifiablePath propertyPath, ConstraintDescriptor constraintDescriptor, Object constraintValidatorPayload, ExpressionLanguageFeatureLevel defaultConstraintExpressionLanguageFeatureLevel, @@ -168,8 +168,8 @@ public final List getConstraintViolationCrea return CollectionHelper.toImmutableList( returnedConstraintViolationCreationContexts ); } - protected final PathImpl getCopyOfBasePath() { - return PathImpl.createCopy( basePath ); + protected final ModifiablePath getCopyOfBasePath() { + return ModifiablePath.createCopy( basePath ); } private ConstraintViolationCreationContext getDefaultConstraintViolationCreationContext() { @@ -188,13 +188,13 @@ private abstract class NodeBuilderBase { protected final String messageTemplate; protected ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel; - protected PathImpl propertyPath; + protected ModifiablePath propertyPath; - protected NodeBuilderBase(String template, PathImpl path) { + protected NodeBuilderBase(String template, ModifiablePath path) { this( template, defaultCustomViolationExpressionLanguageFeatureLevel, path ); } - protected NodeBuilderBase(String template, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, PathImpl path) { + protected NodeBuilderBase(String template, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, ModifiablePath path) { this.messageTemplate = template; this.expressionLanguageFeatureLevel = expressionLanguageFeatureLevel; this.propertyPath = path; @@ -225,7 +225,7 @@ public ConstraintValidatorContext addConstraintViolation() { protected class ConstraintViolationBuilderImpl extends NodeBuilderBase implements HibernateConstraintViolationBuilder { - protected ConstraintViolationBuilderImpl(String template, PathImpl path) { + protected ConstraintViolationBuilderImpl(String template, ModifiablePath path) { super( template, path ); } @@ -274,7 +274,7 @@ public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(St */ private void dropLeafNodeIfRequired() { if ( propertyPath.getLeafNode().getKind() == ElementKind.BEAN ) { - propertyPath = PathImpl.createCopyWithoutLeafNode( propertyPath ); + propertyPath = ModifiablePath.createCopyWithoutLeafNode( propertyPath ); } } } @@ -282,7 +282,7 @@ private void dropLeafNodeIfRequired() { protected class NodeBuilder extends NodeBuilderBase implements NodeBuilderDefinedContext, LeafNodeBuilderDefinedContext, ContainerElementNodeBuilderDefinedContext { - protected NodeBuilder(String template, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, PathImpl path) { + protected NodeBuilder(String template, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, ModifiablePath path) { super( template, expressionLanguageFeatureLevel, path ); } @@ -322,7 +322,7 @@ private class DeferredNodeBuilder extends NodeBuilderBase private DeferredNodeBuilder(String template, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, - PathImpl path, + ModifiablePath path, String nodeName, ElementKind leafNodeKind) { super( template, expressionLanguageFeatureLevel, path ); @@ -334,7 +334,7 @@ private DeferredNodeBuilder(String template, private DeferredNodeBuilder(String template, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, - PathImpl path, + ModifiablePath path, String nodeName, Class leafNodeContainerType, Integer leafNodeTypeArgumentIndex) { 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 bc3f24d5b3..1f4be23f67 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 @@ -8,7 +8,7 @@ import java.util.Map; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.util.stereotypes.Immutable; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; @@ -23,7 +23,7 @@ public class ConstraintViolationCreationContext { private final String message; private final ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel; private final boolean customViolation; - private final PathImpl propertyPath; + private final ModifiablePath propertyPath; @Immutable private final Map messageParameters; @Immutable @@ -33,7 +33,7 @@ public class ConstraintViolationCreationContext { public ConstraintViolationCreationContext(String message, ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel, boolean customViolation, - PathImpl property, + ModifiablePath property, Map messageParameters, Map expressionVariables, Object dynamicPayload) { @@ -58,7 +58,7 @@ public boolean isCustomViolation() { return customViolation; } - public final PathImpl getPath() { + public final ModifiablePath getPath() { return propertyPath; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/CrossParameterConstraintValidatorContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/CrossParameterConstraintValidatorContextImpl.java index 4ec07743bf..cbcf104c6f 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/CrossParameterConstraintValidatorContextImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/CrossParameterConstraintValidatorContextImpl.java @@ -12,7 +12,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintViolationBuilder; import org.hibernate.validator.constraintvalidation.HibernateCrossParameterConstraintValidatorContext; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; @@ -25,7 +25,7 @@ public class CrossParameterConstraintValidatorContextImpl extends ConstraintVali public CrossParameterConstraintValidatorContextImpl(List methodParameterNames, ClockProvider clockProvider, - PathImpl propertyPath, + ModifiablePath propertyPath, ConstraintDescriptor constraintDescriptor, Object constraintValidatorPayload, ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel, @@ -63,7 +63,7 @@ private class CrossParameterConstraintViolationBuilderImpl extends ConstraintVio private final List methodParameterNames; - private CrossParameterConstraintViolationBuilderImpl(List methodParameterNames, String template, PathImpl path) { + private CrossParameterConstraintViolationBuilderImpl(List methodParameterNames, String template, ModifiablePath path) { super( template, path ); this.methodParameterNames = methodParameterNames; } @@ -77,7 +77,7 @@ public NodeBuilderDefinedContext addParameterNode(int index) { } private void dropLeafNode() { - propertyPath = PathImpl.createCopyWithoutLeafNode( propertyPath ); + propertyPath = ModifiablePath.createCopyWithoutLeafNode( propertyPath ); } } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/LambdaBasedValidatorDescriptor.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/LambdaBasedValidatorDescriptor.java index 5b412f0334..a17159d8d9 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/LambdaBasedValidatorDescriptor.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/LambdaBasedValidatorDescriptor.java @@ -4,6 +4,7 @@ */ package org.hibernate.validator.internal.engine.constraintvalidation; +import java.io.Serial; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.EnumSet; @@ -22,6 +23,7 @@ */ class LambdaBasedValidatorDescriptor implements ConstraintValidatorDescriptor { + @Serial private static final long serialVersionUID = 5129757824081595723L; private final Type validatedType; diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/SimpleConstraintTree.java b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/SimpleConstraintTree.java index f19b3f05c7..e0945a329b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/SimpleConstraintTree.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/SimpleConstraintTree.java @@ -27,7 +27,7 @@ * @author Guillaume Smet * @author Marko Bekhta */ -class SimpleConstraintTree extends ConstraintTree { +final class SimpleConstraintTree extends ConstraintTree { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); @@ -36,16 +36,43 @@ public SimpleConstraintTree(ConstraintValidatorManager constraintValidatorManage } @Override - protected void validateConstraints(ValidationContext validationContext, + public boolean validateConstraints(ValidationContext validationContext, ValueContext valueContext) { + ConstraintValidatorContextImpl constraintValidatorContext = doValidateConstraints( validationContext, valueContext ); + if ( constraintValidatorContext != null ) { + for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) { + validationContext.addConstraintFailure( + valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor() + ); + } + return false; + } + return true; + } + + @Override + protected void validateConstraints( + ValidationContext validationContext, ValueContext valueContext, - Collection violatedConstraintValidatorContexts) { + Collection violatedConstraintValidatorContexts + ) { + ConstraintValidatorContextImpl constraintValidatorContext = doValidateConstraints( validationContext, valueContext ); + if ( constraintValidatorContext != null ) { + violatedConstraintValidatorContexts.add( constraintValidatorContext ); + } + } + + private ConstraintValidatorContextImpl doValidateConstraints( + ValidationContext validationContext, + ValueContext valueContext + ) { if ( LOG.isTraceEnabled() ) { if ( validationContext.isShowValidatedValuesInTraceLogs() ) { LOG.tracef( "Validating value %s against constraint defined by %s.", valueContext.getCurrentValidatedValue(), - descriptor ); + descriptor + ); } else { LOG.tracef( "Validating against constraint defined by %s.", descriptor ); @@ -61,8 +88,6 @@ protected void validateConstraints(ValidationContext validationContext, ); // validate - if ( validateSingleConstraint( valueContext, constraintValidatorContext, validator ).isPresent() ) { - violatedConstraintValidatorContexts.add( constraintValidatorContext ); - } + return validateSingleConstraint( valueContext, constraintValidatorContext, validator ); } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/path/MaterializedPath.java b/engine/src/main/java/org/hibernate/validator/internal/engine/path/MaterializedPath.java new file mode 100644 index 0000000000..173a86f244 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/path/MaterializedPath.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.internal.engine.path; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Iterator; + +import jakarta.validation.Path; + +final class MaterializedPath implements Path, Serializable { + + @Serial + private static final long serialVersionUID = -8906501301223202169L; + + private final NodeImpl leafNode; + private final NodeImpl[] nodes; + + MaterializedPath(ModifiablePath path) { + this.leafNode = path.getLeafNode(); + this.nodes = NodeImpl.constructPath( this.leafNode ); + } + + @Override + public Iterator iterator() { + return new NodeImpl.NodeIterator( nodes ); + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MaterializedPath other = (MaterializedPath) o; + return leafNode.samePath( other.leafNode ); + } + + @Override + public int hashCode() { + return leafNode.hashCode(); + } + + @Override + public String toString() { + return ModifiablePath.asString( leafNode ); + } +} 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/ModifiablePath.java similarity index 70% rename from engine/src/main/java/org/hibernate/validator/internal/engine/path/PathImpl.java rename to engine/src/main/java/org/hibernate/validator/internal/engine/path/ModifiablePath.java index e0ec36458d..9c7a95e4b8 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/ModifiablePath.java @@ -6,12 +6,11 @@ import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; +import java.io.Serial; import java.io.Serializable; import java.lang.invoke.MethodHandles; -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; @@ -30,7 +29,8 @@ * @author Gunnar Morling * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI */ -public final class PathImpl implements Path, Serializable { +public final class ModifiablePath implements Path, Serializable { + @Serial private static final long serialVersionUID = 7564511574909882392L; private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); @@ -51,8 +51,6 @@ public final class PathImpl implements Path, Serializable { private static final int INDEX_GROUP = 3; private static final int REMAINING_STRING_GROUP = 5; - private List nodeList; - private boolean nodeListRequiresCopy; private NodeImpl currentLeafNode; private int hashCode; @@ -61,27 +59,25 @@ public final class PathImpl implements Path, Serializable { * 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. - * + * given string. * @throws IllegalArgumentException in case {@code property == null} or * {@code property} cannot be parsed. */ - public static PathImpl createPathFromString(String propertyPath) { + public static ModifiablePath createPathFromString(String propertyPath) { Contracts.assertNotNull( propertyPath, MESSAGES.propertyPathCannotBeNull() ); - if ( propertyPath.length() == 0 ) { + if ( propertyPath.isEmpty() ) { return createRootPath(); } return parseProperty( propertyPath ); } - public static PathImpl createPathForExecutable(ExecutableMetaData executable) { + public static ModifiablePath createPathForExecutable(ExecutableMetaData executable) { Contracts.assertNotNull( executable, "A method is required to create a method return value path." ); - PathImpl path = createRootPath(); + ModifiablePath path = createRootPath(); if ( executable.getKind() == ElementKind.CONSTRUCTOR ) { path.addConstructorNode( executable.getName(), executable.getParameterTypes() ); @@ -93,41 +89,32 @@ public static PathImpl createPathForExecutable(ExecutableMetaData executable) { return path; } - public static PathImpl createRootPath() { - PathImpl path = new PathImpl(); - path.addBeanNode(); - return path; + public static ModifiablePath createRootPath() { + return new ModifiablePath( NodeImpl.ROOT_NODE ); } - public static PathImpl createCopy(PathImpl path) { - return new PathImpl( path ); + public static ModifiablePath createCopy(ModifiablePath path) { + return new ModifiablePath( path ); } - public static PathImpl createCopyWithoutLeafNode(PathImpl path) { - return new PathImpl( path.nodeList.subList( 0, path.nodeList.size() - 1 ) ); + public static ModifiablePath createCopyWithoutLeafNode(ModifiablePath path) { + return new ModifiablePath( path.currentLeafNode.getParent() ); } - public boolean isRootPath() { - return nodeList.size() == 1 && nodeList.get( 0 ).getName() == null; + return currentLeafNode.isRootPath(); } public NodeImpl addPropertyNode(String nodeName) { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createPropertyNode( nodeName, parent ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addContainerElementNode(String nodeName) { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createContainerElementNode( nodeName, parent ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } @@ -144,91 +131,64 @@ public boolean needToAddContainerElementNode(String nodeName) { } public NodeImpl addParameterNode(String nodeName, int index) { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createParameterNode( nodeName, parent, index ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addCrossParameterNode() { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createCrossParameterNode( parent ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addBeanNode() { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createBeanNode( parent ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl addReturnValueNode() { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createReturnValue( parent ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } private NodeImpl addConstructorNode(String name, Class[] parameterTypes) { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createConstructorNode( name, parent, parameterTypes ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } private NodeImpl addMethodNode(String name, Class[] parameterTypes) { - requiresWriteableNodeList(); - NodeImpl parent = currentLeafNode; currentLeafNode = NodeImpl.createMethodNode( name, parent, parameterTypes ); - nodeList.add( currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl makeLeafNodeIterable() { - requiresWriteableNodeList(); - currentLeafNode = NodeImpl.makeIterable( currentLeafNode ); - nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl makeLeafNodeIterableAndSetIndex(Integer index) { - requiresWriteableNodeList(); - currentLeafNode = NodeImpl.makeIterableAndSetIndex( currentLeafNode, index ); - nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) { - requiresWriteableNodeList(); - currentLeafNode = NodeImpl.makeIterableAndSetMapKey( currentLeafNode, key ); - nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } @@ -236,33 +196,23 @@ public NodeImpl makeLeafNodeIterableAndSetMapKey(Object key) { 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 ) { - requiresWriteableNodeList(); - currentLeafNode = NodeImpl.setPropertyValue( currentLeafNode, value ); - nodeList.set( nodeList.size() - 1, currentLeafNode ); - // the property value is not part of the NodeImpl hashCode so we don't need to reset the PathImpl hashCode } return currentLeafNode; } public NodeImpl setLeafNodeTypeParameter(Class containerClass, Integer typeArgumentIndex) { - requiresWriteableNodeList(); - currentLeafNode = NodeImpl.setTypeParameter( currentLeafNode, containerClass, typeArgumentIndex ); - nodeList.set( nodeList.size() - 1, currentLeafNode ); resetHashCode(); return currentLeafNode; } public void removeLeafNode() { - if ( !nodeList.isEmpty() ) { - requiresWriteableNodeList(); - - nodeList.remove( nodeList.size() - 1 ); - currentLeafNode = nodeList.isEmpty() ? null : (NodeImpl) nodeList.get( nodeList.size() - 1 ); + if ( currentLeafNode != null ) { + currentLeafNode = currentLeafNode.getParent(); resetHashCode(); } } @@ -273,49 +223,43 @@ public NodeImpl getLeafNode() { @Override public Iterator iterator() { - if ( nodeList.size() == 0 ) { - return Collections.emptyList().iterator(); - } - if ( nodeList.size() == 1 ) { - return nodeList.iterator(); + if ( currentLeafNode == null ) { + return Collections.emptyIterator(); } - return nodeList.subList( 1, nodeList.size() ).iterator(); + return new NodeImpl.NodeIterator( NodeImpl.constructPath( currentLeafNode ) ); + } + + public Path materialize() { + return new MaterializedPath( this ); } public String asString() { + return asString( currentLeafNode ); + } + + static String asString(NodeImpl currentLeafNode) { 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(); + NodeImpl current = currentLeafNode; + while ( !current.isRootPath() ) { + String name = current.asString(); if ( name.isEmpty() ) { + current = current.getParent(); // 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.insert( 0, PROPERTY_PATH_SEPARATOR ); } - builder.append( nodeImpl.asString() ); - + builder.insert( 0, current.asString() ); first = false; + current = current.getParent(); } 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(); @@ -332,13 +276,13 @@ public boolean equals(Object obj) { if ( getClass() != obj.getClass() ) { return false; } - PathImpl other = (PathImpl) obj; - if ( nodeList == null ) { - if ( other.nodeList != null ) { + ModifiablePath other = (ModifiablePath) obj; + if ( currentLeafNode == null ) { + if ( other.currentLeafNode != null ) { return false; } } - else if ( !nodeList.equals( other.nodeList ) ) { + else if ( !currentLeafNode.samePath( other.currentLeafNode ) ) { return false; } return true; @@ -358,7 +302,7 @@ private int buildHashCode() { final int prime = 31; int result = 1; result = prime * result - + ( ( nodeList == null ) ? 0 : nodeList.hashCode() ); + + ( ( currentLeafNode == null ) ? 0 : currentLeafNode.hashCode() ); return result; } @@ -367,32 +311,26 @@ private int buildHashCode() { * * @param path the path to make a copy of. */ - private PathImpl(PathImpl path) { - nodeList = path.nodeList; + private ModifiablePath(ModifiablePath path) { currentLeafNode = path.currentLeafNode; hashCode = path.hashCode; - nodeListRequiresCopy = true; } - private PathImpl() { - nodeList = new ArrayList<>( 1 ); + private ModifiablePath() { hashCode = -1; - nodeListRequiresCopy = false; } - private PathImpl(List nodeList) { - this.nodeList = nodeList; - currentLeafNode = (NodeImpl) nodeList.get( nodeList.size() - 1 ); + private ModifiablePath(NodeImpl currentLeafNode) { + this.currentLeafNode = currentLeafNode; hashCode = -1; - nodeListRequiresCopy = true; } private void resetHashCode() { hashCode = -1; } - private static PathImpl parseProperty(String propertyName) { - PathImpl path = createRootPath(); + private static ModifiablePath parseProperty(String propertyName) { + ModifiablePath path = createRootPath(); String tmp = propertyName; do { Matcher matcher = PATH_PATTERN.matcher( tmp ); @@ -443,9 +381,7 @@ private static PathImpl parseProperty(String propertyName) { * 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) { @@ -465,37 +401,21 @@ private static boolean isValidJavaIdentifier(String identifier) { /** * checks if this PathImpl is a subpath of other. + * * @param other the path to compare with * @return true, if this path is a subpath */ - public boolean isSubPathOf(PathImpl other) { - if ( nodeList.size() > other.nodeList.size() ) { - // cannot be a subpath as it is already longer then the other path - return false; + public boolean isSubPathOf(ModifiablePath other) { + if ( currentLeafNode == null ) { + return other.currentLeafNode == null; } - - for ( int i = 0; i < nodeList.size(); i++ ) { - if ( !nodeList.get( i ).equals( other.nodeList.get( i ) ) ) { - return false; - } - } - return true; + return currentLeafNode.isSubPathOf( other.currentLeafNode ); } - public boolean isSubPathOrContains(PathImpl other) { - - // prefetch contant return values - int oSize = other.nodeList.size(); - // calling Math.min will reduce speed significantly - int mySize = nodeList.size() < oSize - ? nodeList.size() - : oSize; - - for ( int i = 0; i < mySize; i++ ) { - if ( !nodeList.get( i ).equals( other.nodeList.get( i ) ) ) { - return false; - } + public boolean isSubPathOrContains(ModifiablePath other) { + if ( currentLeafNode == null ) { + return other.currentLeafNode == null; } - return true; + return currentLeafNode.isSubPathOrContains( other.currentLeafNode ); } } 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 7fe01ff67a..52577412fc 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 @@ -4,11 +4,14 @@ */ package org.hibernate.validator.internal.engine.path; +import java.io.Serial; import java.io.Serializable; import java.lang.invoke.MethodHandles; import java.util.Arrays; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import jakarta.validation.ElementKind; import jakarta.validation.Path; @@ -36,11 +39,21 @@ public class NodeImpl implements Path.PropertyNode, Path.MethodNode, Path.ConstructorNode, Path.BeanNode, Path.ParameterNode, Path.ReturnValueNode, Path.CrossParameterNode, Path.ContainerElementNode, org.hibernate.validator.path.PropertyNode, org.hibernate.validator.path.ContainerElementNode, Serializable { + @Serial private static final long serialVersionUID = 2075466571633860499L; private static final Class[] EMPTY_CLASS_ARRAY = new Class[] { }; private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + static final NodeImpl ROOT_NODE; + + static { + ROOT_NODE = NodeImpl.createBeanNode( null ); + ROOT_NODE.valueSet = true; + ROOT_NODE.nodes = new NodeImpl[] { ROOT_NODE }; + ROOT_NODE.hashCode(); + } + private static final String INDEX_OPEN = "["; private static final String INDEX_CLOSE = "]"; private static final String TYPE_PARAMETER_OPEN = "<"; @@ -55,7 +68,9 @@ public class NodeImpl private final String name; private final NodeImpl parent; - private final boolean isIterable; + private final NodeImpl root; + private final int size; + private boolean isIterable; private final Integer index; private final Object key; private final ElementKind kind; @@ -63,20 +78,27 @@ public class NodeImpl //type-specific attributes private final Class[] parameterTypes; private final Integer parameterIndex; - private final Object value; + private Object value; + private boolean valueSet; private final Class containerClass; private final Integer typeArgumentIndex; private int hashCode = -1; private String asString; + private NodeImpl[] nodes; - private NodeImpl(String name, NodeImpl parent, boolean isIterable, Integer index, Object key, ElementKind kind, Class[] parameterTypes, - Integer parameterIndex, Object value, Class containerClass, Integer typeArgumentIndex) { + private NodeImpl( + String name, NodeImpl parent, boolean isIterable, Integer index, Object key, ElementKind kind, Class[] parameterTypes, + Integer parameterIndex, Object value, boolean valueSet, Class containerClass, Integer typeArgumentIndex + ) { this.name = name; this.parent = parent; + this.root = parent == null ? this : parent.root; + this.size = ( parent == null ? 0 : parent.size ) + 1; this.index = index; this.key = key; this.value = value; + this.valueSet = valueSet; this.isIterable = isIterable; this.kind = kind; this.parameterTypes = parameterTypes; @@ -97,6 +119,7 @@ public static NodeImpl createPropertyNode(String name, NodeImpl parent) { EMPTY_CLASS_ARRAY, null, null, + false, null, null ); @@ -113,6 +136,7 @@ public static NodeImpl createContainerElementNode(String name, NodeImpl parent) EMPTY_CLASS_ARRAY, null, null, + false, null, null ); @@ -129,6 +153,7 @@ public static NodeImpl createParameterNode(String name, NodeImpl parent, int par EMPTY_CLASS_ARRAY, parameterIndex, null, + false, null, null ); @@ -145,17 +170,18 @@ public static NodeImpl createCrossParameterNode(NodeImpl parent) { EMPTY_CLASS_ARRAY, null, null, + false, null, null ); } public static NodeImpl createMethodNode(String name, NodeImpl parent, Class[] parameterTypes) { - return new NodeImpl( name, parent, false, null, null, ElementKind.METHOD, parameterTypes, null, null, null, null ); + return new NodeImpl( name, parent, false, null, null, ElementKind.METHOD, parameterTypes, null, null, false, null, null ); } public static NodeImpl createConstructorNode(String name, NodeImpl parent, Class[] parameterTypes) { - return new NodeImpl( name, parent, false, null, null, ElementKind.CONSTRUCTOR, parameterTypes, null, null, null, null ); + return new NodeImpl( name, parent, false, null, null, ElementKind.CONSTRUCTOR, parameterTypes, null, null, false, null, null ); } public static NodeImpl createBeanNode(NodeImpl parent) { @@ -169,6 +195,7 @@ public static NodeImpl createBeanNode(NodeImpl parent) { EMPTY_CLASS_ARRAY, null, null, + false, null, null ); @@ -185,6 +212,7 @@ public static NodeImpl createReturnValue(NodeImpl parent) { EMPTY_CLASS_ARRAY, null, null, + false, null, null ); @@ -201,6 +229,7 @@ public static NodeImpl makeIterable(NodeImpl node) { node.parameterTypes, node.parameterIndex, node.value, + node.valueSet, node.containerClass, node.typeArgumentIndex ); @@ -217,6 +246,7 @@ public static NodeImpl makeIterableAndSetIndex(NodeImpl node, Integer index) { node.parameterTypes, node.parameterIndex, node.value, + node.valueSet, node.containerClass, node.typeArgumentIndex ); @@ -233,28 +263,38 @@ public static NodeImpl makeIterableAndSetMapKey(NodeImpl node, Object key) { node.parameterTypes, node.parameterIndex, node.value, + node.valueSet, node.containerClass, node.typeArgumentIndex ); } public static NodeImpl setPropertyValue(NodeImpl node, Object value) { - return new NodeImpl( - node.name, - node.parent, - node.isIterable, - node.index, - node.key, - node.kind, - node.parameterTypes, - node.parameterIndex, - value, - node.containerClass, - node.typeArgumentIndex - ); + if ( node.valueSet && node.value != value ) { + return new NodeImpl( + node.name, + node.parent, + node.isIterable, + node.index, + node.key, + node.kind, + node.parameterTypes, + node.parameterIndex, + value, + true, + node.containerClass, + node.typeArgumentIndex + ); + } + node.value = value; + node.valueSet = true; + return node; } public static NodeImpl setTypeParameter(NodeImpl node, Class containerClass, Integer typeArgumentIndex) { + if ( node.typeArgumentIndex != null && node.typeArgumentIndex.equals( typeArgumentIndex ) && node.containerClass == containerClass ) { + return node; + } return new NodeImpl( node.name, node.parent, @@ -265,6 +305,7 @@ public static NodeImpl setTypeParameter(NodeImpl node, Class containerClass, node.parameterTypes, node.parameterIndex, node.value, + node.valueSet, containerClass, typeArgumentIndex ); @@ -362,7 +403,7 @@ public int getParameterIndex() { kind == ElementKind.PARAMETER, "getParameterIndex() may only be invoked for nodes of type ElementKind.PARAMETER." ); - return parameterIndex.intValue(); + return parameterIndex; } @Override @@ -461,6 +502,19 @@ public boolean equals(Object obj) { return false; } NodeImpl other = (NodeImpl) obj; + return samePath( other ); + } + + boolean sameNode(NodeImpl other) { + if ( this == other ) { + return true; + } + if ( other == null ) { + return false; + } + if ( hashCode != -1 && other.hashCode != -1 && hashCode != other.hashCode ) { + return false; + } if ( index == null ) { if ( other.index != null ) { return false; @@ -525,4 +579,117 @@ else if ( !Arrays.equals( parameterTypes, other.parameterTypes ) ) { } return true; } + + boolean samePath(NodeImpl other) { + if ( this.size != other.size ) { + return false; + } + NodeImpl curr = this; + NodeImpl otherCurr = other; + while ( curr != null && otherCurr != null ) { + if ( !curr.sameNode( otherCurr ) ) { + return false; + } + otherCurr = otherCurr.parent; + curr = curr.parent; + } + + return curr == null && otherCurr == null; + } + + public boolean isRootPath() { + return parent == null && name == null; + } + + static NodeImpl[] constructPath(NodeImpl leaf) { + if ( leaf.parent == null ) { + if ( leaf.nodes == null ) { + leaf.nodes = new NodeImpl[] { leaf }; + } + } + else { + leaf.nodes = new NodeImpl[leaf.size - 1]; + NodeImpl curr = leaf; + while ( curr.parent != null ) { + leaf.nodes[curr.size - 2] = curr; + curr = curr.parent; + } + } + + return leaf.nodes; + } + + boolean isSubPathOf(NodeImpl other) { + if ( this.size > other.size ) { + return false; + } + NodeImpl curr = this; + NodeImpl otherCurr = other; + while ( otherCurr != null && !otherCurr.equals( this ) ) { + otherCurr = otherCurr.parent; + } + if ( otherCurr == null ) { + return false; + } + while ( !curr.isRootPath() && !otherCurr.isRootPath() ) { + if ( !curr.equals( otherCurr ) ) { + return false; + } + curr = curr.parent; + otherCurr = otherCurr.parent; + } + + return curr.isRootPath(); + } + + public boolean isSubPathOrContains(NodeImpl other) { + NodeImpl curr; + NodeImpl otherCurr; + if ( this.size > other.size ) { + curr = other; + otherCurr = this; + } + else { + curr = this; + otherCurr = other; + } + + while ( otherCurr != null && !otherCurr.equals( curr ) ) { + otherCurr = otherCurr.parent; + } + if ( otherCurr == null ) { + return false; + } + while ( !curr.isRootPath() && !otherCurr.isRootPath() ) { + if ( !curr.equals( otherCurr ) ) { + return false; + } + curr = curr.parent; + otherCurr = otherCurr.parent; + } + + return curr.isRootPath() && otherCurr.isRootPath(); + } + + protected static class NodeIterator implements Iterator { + private final NodeImpl[] array; + private int index; + + public NodeIterator(NodeImpl[] array) { + this.array = array; + } + + @Override + public boolean hasNext() { + return index < array.length; + } + + @Override + public Path.Node next() { + if ( index < array.length ) { + return array[index++]; + } + throw new NoSuchElementException(); + } + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java index bef5b4560a..48b653998b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java @@ -24,7 +24,8 @@ 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.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; +import org.hibernate.validator.internal.engine.path.NodeImpl; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.core.MetaConstraint; @@ -109,10 +110,10 @@ abstract class AbstractValidationContext implements BaseBeanValidationContext private Set processedGroupUnits; /** - * Maps an object to a list of paths in which it has been validated. The objects are the bean instances. + * Maps an object to a list of paths (represented by leaf nodes) in which it has been validated. The objects are the bean instances. */ @Lazy - private Map> processedPathsPerBean; + private Map> processedPathsPerBean; /** * Contains all failing constraints so far. @@ -195,16 +196,15 @@ public ConstraintValidatorFactory getConstraintValidatorFactory() { } @Override - public boolean isBeanAlreadyValidated(Object value, Class group, PathImpl path) { + public boolean isBeanAlreadyValidated(Object value, Class group, ModifiablePath path) { if ( disableAlreadyValidatedBeanTracking ) { return false; } - boolean alreadyValidated; - alreadyValidated = isAlreadyValidatedForCurrentGroup( value, group ); + boolean alreadyValidated = isAlreadyValidatedForCurrentGroup( value, group ); if ( alreadyValidated ) { - alreadyValidated = isAlreadyValidatedForPath( value, path ); + alreadyValidated = isAlreadyValidatedForPath( value, path.getLeafNode() ); } return alreadyValidated; @@ -247,7 +247,7 @@ public void addConstraintFailure( 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().materialize(); getInitializedFailingConstraintViolations().add( createConstraintViolation( @@ -270,7 +270,7 @@ protected abstract ConstraintViolation createConstraintViolation( ConstraintViolationCreationContext constraintViolationCreationContext); @Override - public boolean hasMetaConstraintBeenProcessed(Object bean, Path path, MetaConstraint metaConstraint) { + public boolean hasMetaConstraintBeenProcessed(Object bean, ModifiablePath path, MetaConstraint metaConstraint) { // this is only useful if the constraint is defined for more than 1 group as in the case it's only // defined for one group, there is no chance it's going to be called twice. if ( metaConstraint.isDefinedForOneGroupOnly() ) { @@ -281,7 +281,7 @@ public boolean hasMetaConstraintBeenProcessed(Object bean, Path path, MetaConstr } @Override - public void markConstraintProcessed(Object bean, Path path, MetaConstraint metaConstraint) { + public void markConstraintProcessed(Object bean, ModifiablePath path, MetaConstraint metaConstraint) { // this is only useful if the constraint is defined for more than 1 group as in the case it's only // defined for one group, there is no chance it's going to be called twice. if ( metaConstraint.isDefinedForOneGroupOnly() ) { @@ -292,7 +292,7 @@ public void markConstraintProcessed(Object bean, Path path, MetaConstraint me } @Override - public ConstraintValidatorContextImpl createConstraintValidatorContextFor(ConstraintDescriptorImpl constraintDescriptor, PathImpl path) { + public ConstraintValidatorContextImpl createConstraintValidatorContextFor(ConstraintDescriptorImpl constraintDescriptor, ModifiablePath path) { return new ConstraintValidatorContextImpl( validatorScopedContext.getClockProvider(), path, @@ -340,8 +340,8 @@ private String interpolate( } } - private boolean isAlreadyValidatedForPath(Object value, PathImpl path) { - Set pathSet = getInitializedProcessedPathsPerBean().get( value ); + private boolean isAlreadyValidatedForPath(Object value, NodeImpl path) { + Set pathSet = getInitializedProcessedPathsPerBean().get( value ); if ( pathSet == null ) { return false; } @@ -354,7 +354,7 @@ private boolean isAlreadyValidatedForPath(Object value, PathImpl path) { // it means that the new path we are testing cannot be a root path; also since we are cascading into inner // objects, i.e. going further from the object tree root, it means that the new path cannot be shorter than // the ones we've already encountered. - for ( PathImpl p : pathSet ) { + for ( NodeImpl p : pathSet ) { if ( p.isSubPathOrContains( path ) ) { return true; } @@ -366,17 +366,17 @@ private boolean isAlreadyValidatedForCurrentGroup(Object value, Class group) return getInitializedProcessedGroupUnits().contains( new BeanGroupProcessedUnit( value, group ) ); } - private void markCurrentBeanAsProcessedForCurrentPath(Object bean, PathImpl path) { - // HV-1031 The path object is mutated as we traverse the object tree, hence copy it before saving it - Map> processedPathsPerBean = getInitializedProcessedPathsPerBean(); + private void markCurrentBeanAsProcessedForCurrentPath(Object bean, ModifiablePath path) { + Map> processedPathsPerBean = getInitializedProcessedPathsPerBean(); - Set processedPaths = processedPathsPerBean.get( bean ); + Set processedPaths = processedPathsPerBean.get( bean ); if ( processedPaths == null ) { processedPaths = new HashSet<>(); processedPathsPerBean.put( bean, processedPaths ); } - processedPaths.add( PathImpl.createCopy( path ) ); + // HV-1031 The path object is mutated as we traverse the object tree, hence we use the node which is not (for the most part): + processedPaths.add( path.getLeafNode() ); } private void markCurrentBeanAsProcessedForCurrentGroup(Object bean, Class group) { @@ -397,7 +397,7 @@ private Set getInitializedProcessedGroupUnits() { return processedGroupUnits; } - private Map> getInitializedProcessedPathsPerBean() { + private Map> getInitializedProcessedPathsPerBean() { if ( processedPathsPerBean == null ) { processedPathsPerBean = new IdentityHashMap<>(); } @@ -415,13 +415,13 @@ 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 NodeImpl pathLeaf; private MetaConstraint metaConstraint; private int hashCode; - BeanPathMetaConstraintProcessedUnit(Object bean, Path path, MetaConstraint metaConstraint) { + BeanPathMetaConstraintProcessedUnit(Object bean, ModifiablePath path, MetaConstraint metaConstraint) { this.bean = bean; - this.path = path; + this.pathLeaf = path.getLeafNode(); // because the leaf represent the entire path. this.metaConstraint = metaConstraint; this.hashCode = createHashCode(); } @@ -442,7 +442,7 @@ public boolean equals(Object o) { if ( metaConstraint != that.metaConstraint ) { return false; } - if ( !path.equals( that.path ) ) { + if ( !pathLeaf.equals( that.pathLeaf ) ) { return false; } @@ -456,7 +456,7 @@ public int hashCode() { private int createHashCode() { int result = System.identityHashCode( bean ); - result = 31 * result + path.hashCode(); + result = 31 * result + pathLeaf.hashCode(); result = 31 * result + System.identityHashCode( metaConstraint ); return result; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BaseBeanValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BaseBeanValidationContext.java index e807cbff45..4f8158eecb 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BaseBeanValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BaseBeanValidationContext.java @@ -4,12 +4,11 @@ */ package org.hibernate.validator.internal.engine.validationcontext; -import jakarta.validation.Path; import jakarta.validation.TraversableResolver; import jakarta.validation.Validator; import org.hibernate.validator.internal.engine.ValidatorImpl; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.core.MetaConstraint; @@ -35,13 +34,13 @@ public interface BaseBeanValidationContext extends ValidationContext { TraversableResolver getTraversableResolver(); - boolean isBeanAlreadyValidated(Object value, Class group, PathImpl path); + boolean isBeanAlreadyValidated(Object value, Class group, ModifiablePath path); void markCurrentBeanAsProcessed(ValueContext valueContext); - boolean hasMetaConstraintBeenProcessed(Object bean, Path path, MetaConstraint metaConstraint); + boolean hasMetaConstraintBeenProcessed(Object bean, ModifiablePath path, MetaConstraint metaConstraint); - void markConstraintProcessed(Object bean, Path path, MetaConstraint metaConstraint); + void markConstraintProcessed(Object bean, ModifiablePath path, MetaConstraint metaConstraint); /** * @return {@code true} if current validation context can and should process passed meta constraint. Is used in diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java index ae7cf85f1c..431c5feeae 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ParameterExecutableValidationContext.java @@ -21,7 +21,7 @@ import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintViolationCreationContext; import org.hibernate.validator.internal.engine.constraintvalidation.CrossParameterConstraintValidatorContextImpl; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; @@ -93,7 +93,7 @@ private static boolean buildDisableAlreadyValidatedBeanTracking(Optional constraintDescriptor, PathImpl path) { + public ConstraintValidatorContextImpl createConstraintValidatorContextFor(ConstraintDescriptorImpl constraintDescriptor, ModifiablePath path) { if ( ConstraintType.CROSS_PARAMETER.equals( constraintDescriptor.getConstraintType() ) ) { return new CrossParameterConstraintValidatorContextImpl( getParameterNames(), diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContext.java index 88fe408920..84ed56bd16 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContext.java @@ -17,7 +17,7 @@ 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.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.engine.valuecontext.ValueContext; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; @@ -56,5 +56,5 @@ void addConstraintFailure( Set> getFailingConstraints(); - ConstraintValidatorContextImpl createConstraintValidatorContextFor(ConstraintDescriptorImpl constraintDescriptor, PathImpl path); + ConstraintValidatorContextImpl createConstraintValidatorContextFor(ConstraintDescriptorImpl constraintDescriptor, ModifiablePath path); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java index ea511085b7..0440b21f7c 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java @@ -11,7 +11,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; /** @@ -54,7 +54,7 @@ public BaseBeanValidationContext forValidate(Class rootBeanClass, Bean ); } - public BaseBeanValidationContext forValidateProperty(Class rootBeanClass, BeanMetaData rootBeanMetaData, T rootBean, PathImpl propertyPath) { + public BaseBeanValidationContext forValidateProperty(Class rootBeanClass, BeanMetaData rootBeanMetaData, T rootBean, ModifiablePath propertyPath) { return new PropertyValidationContext<>( constraintValidatorManager, constraintValidatorFactory, @@ -68,7 +68,7 @@ public BaseBeanValidationContext forValidateProperty(Class rootBeanCla ); } - public BaseBeanValidationContext forValidateValue(Class rootBeanClass, BeanMetaData rootBeanMetaData, PathImpl propertyPath) { + public BaseBeanValidationContext forValidateValue(Class rootBeanClass, BeanMetaData rootBeanMetaData, ModifiablePath propertyPath) { return new PropertyValidationContext<>( constraintValidatorManager, constraintValidatorFactory, diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/BeanValueContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/BeanValueContext.java index d2112243af..bbb3c3ec2e 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/BeanValueContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/BeanValueContext.java @@ -4,7 +4,7 @@ */ package org.hibernate.validator.internal.engine.valuecontext; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -18,7 +18,7 @@ public class BeanValueContext extends ValueContext { */ private final BeanMetaData currentBeanMetaData; - BeanValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, BeanMetaData currentBeanMetaData, PathImpl propertyPath) { + BeanValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, BeanMetaData currentBeanMetaData, ModifiablePath propertyPath) { super( parameterNameProvider, currentBean, currentBeanMetaData, propertyPath ); this.currentBeanMetaData = currentBeanMetaData; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContext.java index 82754018e4..850dc84489 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContext.java @@ -7,7 +7,7 @@ import java.lang.reflect.TypeVariable; import org.hibernate.validator.internal.engine.groups.Group; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.metadata.facets.Cascadable; @@ -37,7 +37,7 @@ public class ValueContext { /** * The current property path we are validating. */ - private PathImpl propertyPath; + private ModifiablePath propertyPath; /** * The current group we are validating. @@ -56,14 +56,14 @@ public class ValueContext { */ private ConstraintLocationKind constraintLocationKind; - ValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, Validatable validatable, PathImpl propertyPath) { + ValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, Validatable validatable, ModifiablePath propertyPath) { this.parameterNameProvider = parameterNameProvider; this.currentBean = currentBean; this.currentValidatable = validatable; this.propertyPath = propertyPath; } - public final PathImpl getPropertyPath() { + public final ModifiablePath getPropertyPath() { return propertyPath; } @@ -87,20 +87,20 @@ public final Object getCurrentValidatedValue() { } public final void appendNode(Cascadable node) { - PathImpl newPath = PathImpl.createCopy( propertyPath ); + ModifiablePath newPath = ModifiablePath.createCopy( propertyPath ); node.appendTo( newPath ); propertyPath = newPath; } public final void appendNode(ConstraintLocation location) { - PathImpl newPath = PathImpl.createCopy( propertyPath ); + ModifiablePath newPath = ModifiablePath.createCopy( propertyPath ); location.appendTo( parameterNameProvider, newPath ); propertyPath = newPath; } public final void appendTypeParameterNode(String nodeName) { if ( propertyPath.needToAddContainerElementNode( nodeName ) ) { - PathImpl newPath = PathImpl.createCopy( propertyPath ); + ModifiablePath newPath = ModifiablePath.createCopy( propertyPath ); newPath.addContainerElementNode( nodeName ); propertyPath = newPath; } @@ -187,16 +187,16 @@ public Object getValue(Object parent, ConstraintLocation location) { public static class ValueState { - private final PathImpl propertyPath; + private final ModifiablePath propertyPath; private final V currentValue; - ValueState(PathImpl propertyPath, V currentValue) { + ValueState(ModifiablePath propertyPath, V currentValue) { this.propertyPath = propertyPath; this.currentValue = currentValue; } - public PathImpl getPropertyPath() { + public ModifiablePath getPropertyPath() { return propertyPath; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContexts.java b/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContexts.java index 53879ba466..6c76616e44 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContexts.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/ValueContexts.java @@ -4,7 +4,7 @@ */ package org.hibernate.validator.internal.engine.valuecontext; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.facets.Validatable; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -25,7 +25,7 @@ public static ValueContext getLocalExecutionContextForExecutable( ExecutableParameterNameProvider parameterNameProvider, T value, Validatable validatable, - PathImpl propertyPath) { + ModifiablePath propertyPath) { return new ValueContext<>( parameterNameProvider, value, validatable, propertyPath ); } @@ -34,7 +34,7 @@ public static BeanValueContext getLocalExecutionContextForBean( ExecutableParameterNameProvider parameterNameProvider, T value, BeanMetaData currentBeanMetaData, - PathImpl propertyPath) { + ModifiablePath propertyPath) { return new BeanValueContext<>( parameterNameProvider, value, (BeanMetaData) currentBeanMetaData, propertyPath ); } @@ -42,7 +42,7 @@ public static BeanValueContext getLocalExecutionContextForBean( public static BeanValueContext getLocalExecutionContextForValueValidation( ExecutableParameterNameProvider parameterNameProvider, BeanMetaData currentBeanMetaData, - PathImpl propertyPath) { + ModifiablePath propertyPath) { return new BeanValueContext<>( parameterNameProvider, null, (BeanMetaData) currentBeanMetaData, propertyPath ); } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java index c91124fb75..939f3c1c43 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.properties.Field; @@ -45,7 +45,7 @@ public Object getValue(Object parent) { } @Override - public void appendTo(PathImpl path) { + public void appendTo(ModifiablePath path) { path.addPropertyNode( property.getResolvedPropertyName() ); } 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 5ea180f499..ffd1af4f33 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 @@ -12,7 +12,7 @@ import jakarta.validation.metadata.ParameterDescriptor; import org.hibernate.validator.internal.engine.ConstraintCreationContext; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.ParameterDescriptorImpl; import org.hibernate.validator.internal.metadata.facets.Cascadable; @@ -85,7 +85,7 @@ public Type getCascadableType() { } @Override - public void appendTo(PathImpl path) { + public void appendTo(ModifiablePath 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 ca00aed6d3..2b74f79a82 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 @@ -12,7 +12,7 @@ import jakarta.validation.ElementKind; import jakarta.validation.metadata.ReturnValueDescriptor; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; 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(ModifiablePath 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 9f8b722bf9..e514e78f92 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 @@ -128,9 +128,8 @@ public boolean validateConstraint(ValidationContext validationContext, ValueC private boolean doValidateConstraint(ValidationContext executionContext, ValueContext valueContext) { valueContext.setConstraintLocationKind( getConstraintLocationKind() ); - boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext ); - return validationResult; + return constraintTree.validateConstraints( executionContext, valueContext ); } public ConstraintLocation getLocation() { diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/descriptor/ConstraintDescriptorImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/descriptor/ConstraintDescriptorImpl.java index 64ff15b267..450e8a60aa 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/descriptor/ConstraintDescriptorImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/descriptor/ConstraintDescriptorImpl.java @@ -8,6 +8,7 @@ import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; import static org.hibernate.validator.internal.util.CollectionHelper.newLinkedHashSet; +import java.io.Serial; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; @@ -66,6 +67,7 @@ */ public class ConstraintDescriptorImpl implements ConstraintDescriptor, Serializable { + @Serial private static final long serialVersionUID = -2563102960314069246L; private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); private static final int OVERRIDES_PARAMETER_DEFAULT_INDEX = -1; 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 d343dc676d..7d3ea1b430 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 @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; @@ -44,7 +44,7 @@ public interface Cascadable { /** * Appends this cascadable element to the given path. */ - void appendTo(PathImpl path); + void appendTo(ModifiablePath 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/AbstractPropertyConstraintLocation.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/AbstractPropertyConstraintLocation.java index 1766236eae..337609979f 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/location/AbstractPropertyConstraintLocation.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/location/AbstractPropertyConstraintLocation.java @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.properties.Property; import org.hibernate.validator.internal.properties.PropertyAccessor; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -51,7 +51,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath path) { path.addPropertyNode( property.getResolvedPropertyName() ); } 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 ea16562a52..4dd9a387cf 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 @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.properties.Constrainable; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeHelper; @@ -54,7 +54,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath 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 9f9cf21d34..b9463f0bc9 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 @@ -8,7 +8,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.ModifiablePath; import org.hibernate.validator.internal.metadata.raw.ConstrainedElement.ConstrainedElementKind; import org.hibernate.validator.internal.properties.Callable; import org.hibernate.validator.internal.properties.Constrainable; @@ -88,7 +88,7 @@ static ConstraintLocation forParameter(Callable callable, int index) { /** * Appends a node representing this location to the given property path. */ - void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path); + void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath 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 f199300d58..9e066cc66e 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 @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.properties.Callable; import org.hibernate.validator.internal.properties.Constrainable; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -45,7 +45,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath path) { path.addCrossParameterNode(); } 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 67a7266796..d0685c9cd2 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 @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.properties.Callable; import org.hibernate.validator.internal.properties.Constrainable; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -53,7 +53,7 @@ public int getIndex() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath path) { path.addParameterNode( callable.getParameterName( parameterNameProvider, index ), 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 f1947d8a01..1cb303f41f 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 @@ -6,7 +6,7 @@ import java.lang.reflect.Type; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.properties.Callable; import org.hibernate.validator.internal.properties.Constrainable; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -46,7 +46,7 @@ public Type getTypeForValidatorResolution() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath 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 45b6d4b803..fc9c9f6b2c 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 @@ -7,7 +7,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.ModifiablePath; import org.hibernate.validator.internal.properties.Constrainable; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.ReflectionHelper; @@ -68,7 +68,7 @@ public Class getContainerClass() { } @Override - public void appendTo(ExecutableParameterNameProvider parameterNameProvider, PathImpl path) { + public void appendTo(ExecutableParameterNameProvider parameterNameProvider, ModifiablePath path) { delegate.appendTo( parameterNameProvider, path ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java b/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java index 1ef9568662..b38e49b844 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java @@ -7,14 +7,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -95,38 +93,47 @@ public static Set asSet(T... ts) { return new HashSet<>( Arrays.asList( ts ) ); } + /** + * Converts the provided list to an {@code java.util.ImmutableCollections}. + *

+ * NOTE: the resulting list does not allow {@code null} values. + * Attempt to convert a list with {@code null}s will result in an exception! + * + * @param list the list to convert. + * @return the converted list. + * @param the type of the list elements. + */ public static List toImmutableList(List list) { - switch ( list.size() ) { - case 0: - return Collections.emptyList(); - case 1: - return Collections.singletonList( list.get( 0 ) ); - default: - return Collections.unmodifiableList( list ); - } + return List.copyOf( list ); } + /** + * Converts the provided set to an {@code java.util.ImmutableCollections}. + *

+ * NOTE: the resulting set does not allow {@code null} values. + * Attempt to convert a set with {@code null}s will result in an exception! + * + * @param set the set to convert. + * @return the converted set. + * @param the type of the set elements. + */ public static Set toImmutableSet(Set set) { - switch ( set.size() ) { - case 0: - return Collections.emptySet(); - case 1: - return Collections.singleton( set.iterator().next() ); - default: - return Collections.unmodifiableSet( set ); - } + return Set.copyOf( set ); } + /** + * Converts the provided map to an {@code java.util.ImmutableCollections}. + *

+ * NOTE: the resulting map does not allow {@code null} keys and values . + * Attempt to convert a map with {@code null}s (either as key or a value) will result in an exception! + * + * @param map the map to convert. + * @param the type of the map keys. + * @param the type of the map values. + * @return the converted map. + */ public static Map toImmutableMap(Map map) { - switch ( map.size() ) { - case 0: - return Collections.emptyMap(); - case 1: - Entry entry = map.entrySet().iterator().next(); - return Collections.singletonMap( entry.getKey(), entry.getValue() ); - default: - return Collections.unmodifiableMap( map ); - } + return Map.copyOf( map ); } /** diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/ConcurrentReferenceHashMap.java b/engine/src/main/java/org/hibernate/validator/internal/util/ConcurrentReferenceHashMap.java index 6811690bfe..b61dcb19af 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/ConcurrentReferenceHashMap.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/ConcurrentReferenceHashMap.java @@ -12,6 +12,7 @@ package org.hibernate.validator.internal.util; import java.io.IOException; +import java.io.Serial; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; @@ -132,6 +133,7 @@ */ final public class ConcurrentReferenceHashMap extends AbstractMap implements java.util.concurrent.ConcurrentMap, Serializable { + @Serial private static final long serialVersionUID = 7249069246763182397L; /* @@ -476,7 +478,7 @@ static final class Segment extends ReentrantLock implements Serializable { * As a guide, all critical volatile reads and writes to the * count field are marked in code comments. */ - + @Serial private static final long serialVersionUID = 2249069246763182397L; /** @@ -1544,6 +1546,7 @@ public V nextElement() { static class SimpleEntry implements Entry, java.io.Serializable { + @Serial private static final long serialVersionUID = -8499721149061103585L; private final K key; @@ -1605,6 +1608,7 @@ private static boolean eq(Object o1, Object o2) { * changes to the underlying map. */ final class WriteThroughEntry extends SimpleEntry { + @Serial private static final long serialVersionUID = -7900634345345313646L; WriteThroughEntry(K k, V v) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/annotation/AnnotationProxy.java b/engine/src/main/java/org/hibernate/validator/internal/util/annotation/AnnotationProxy.java index a580cedf39..168c89d607 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/annotation/AnnotationProxy.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/annotation/AnnotationProxy.java @@ -4,6 +4,7 @@ */ package org.hibernate.validator.internal.util.annotation; +import java.io.Serial; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; @@ -36,6 +37,7 @@ */ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { + @Serial private static final long serialVersionUID = 6907601010599429454L; private final AnnotationDescriptor descriptor; 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 aee0ee8792..cf8477a739 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 @@ -18,7 +18,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.ModifiablePath; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.testutil.ConstraintViolationAssert.PathExpectation; @@ -223,7 +223,7 @@ public void testContainerElementNode() { } private ConstraintValidatorContextImpl createEmptyConstraintValidatorContextImpl() { - PathImpl path = PathImpl.createRootPath(); + ModifiablePath path = ModifiablePath.createRootPath(); path.addBeanNode(); ConstraintValidatorContextImpl context = new ConstraintValidatorContextImpl( null, path, null, null, ExpressionLanguageFeatureLevel.BEAN_PROPERTIES, 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/ModifiablePathTest.java similarity index 78% rename from engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java rename to engine/src/test/java/org/hibernate/validator/test/internal/engine/path/ModifiablePathTest.java index f06fe1a2c1..98353e4d1a 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/ModifiablePathTest.java @@ -11,6 +11,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.lang.reflect.Method; @@ -30,7 +31,7 @@ import org.hibernate.validator.internal.engine.DefaultPropertyNodeNameProvider; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; @@ -50,12 +51,12 @@ * @author Gunnar Morling * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI */ -public class PathImplTest { +public class ModifiablePathTest { @Test public void testParsing() { String property = "orders[3].deliveryAddress.addressline[1]"; - Path path = PathImpl.createPathFromString( property ); + Path path = ModifiablePath.createPathFromString( property ); Iterator propIter = path.iterator(); assertTrue( propIter.hasNext() ); @@ -76,7 +77,7 @@ public void testParsing() { assertTrue( propIter.hasNext() ); elem = propIter.next(); - assertEquals( elem.getName(), null ); + assertNull( elem.getName() ); assertTrue( elem.isInIterable() ); assertEquals( elem.getIndex(), Integer.valueOf( 1 ) ); @@ -87,7 +88,7 @@ public void testParsing() { @Test public void testParsingPropertyWithCurrencySymbol() { - PathImpl path = PathImpl.createPathFromString( "€Amount" ); + ModifiablePath path = ModifiablePath.createPathFromString( "€Amount" ); Iterator it = path.iterator(); assertEquals( it.next().getName(), "€Amount" ); @@ -95,7 +96,7 @@ public void testParsingPropertyWithCurrencySymbol() { @Test public void testParsingPropertyWithGermanCharacter() { - PathImpl path = PathImpl.createPathFromString( "höchstBetrag" ); + ModifiablePath path = ModifiablePath.createPathFromString( "höchstBetrag" ); Iterator it = path.iterator(); assertEquals( it.next().getName(), "höchstBetrag" ); @@ -103,7 +104,7 @@ public void testParsingPropertyWithGermanCharacter() { @Test public void testParsingPropertyWithUnicodeCharacter() { - PathImpl path = PathImpl.createPathFromString( "höchst\u00f6Betrag" ); + ModifiablePath path = ModifiablePath.createPathFromString( "höchst\u00f6Betrag" ); Iterator it = path.iterator(); assertEquals( it.next().getName(), "höchst\u00f6Betrag" ); @@ -111,68 +112,68 @@ public void testParsingPropertyWithUnicodeCharacter() { @Test(expectedExceptions = IllegalArgumentException.class) public void testParsingInvalidJavaProperty() { - PathImpl.createPathFromString( "1invalid" ); + ModifiablePath.createPathFromString( "1invalid" ); } @Test public void testParseMapBasedProperty() { String property = "order[foo].deliveryAddress"; - Path path = PathImpl.createPathFromString( property ); + Path path = ModifiablePath.createPathFromString( property ); Iterator propIter = path.iterator(); assertTrue( propIter.hasNext() ); Path.Node elem = propIter.next(); - assertEquals( "order", elem.getName() ); + assertEquals( elem.getName(), "order" ); assertFalse( elem.isInIterable() ); assertTrue( propIter.hasNext() ); elem = propIter.next(); - assertEquals( "deliveryAddress", elem.getName() ); + assertEquals( elem.getName(), "deliveryAddress" ); assertTrue( elem.isInIterable() ); - assertEquals( "foo", elem.getKey() ); + assertEquals( elem.getKey(), "foo" ); assertFalse( propIter.hasNext() ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testNull() { - PathImpl.createPathFromString( null ); + ModifiablePath.createPathFromString( null ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testUnbalancedBraces() { - PathImpl.createPathFromString( "foo[.bar" ); + ModifiablePath.createPathFromString( "foo[.bar" ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testIndexInMiddleOfProperty() { - PathImpl.createPathFromString( "f[1]oo.bar" ); + ModifiablePath.createPathFromString( "f[1]oo.bar" ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testTrailingPathSeparator() { - PathImpl.createPathFromString( "foo.bar." ); + ModifiablePath.createPathFromString( "foo.bar." ); } @Test(expectedExceptions = IllegalArgumentException.class) public void testLeadingPathSeparator() { - PathImpl.createPathFromString( ".foo.bar" ); + ModifiablePath.createPathFromString( ".foo.bar" ); } @Test public void testEmptyString() { - Path path = PathImpl.createPathFromString( "" ); + Path path = ModifiablePath.createPathFromString( "" ); assertTrue( path.iterator().hasNext() ); } @Test public void testIsSubPathOf() { - PathImpl subPath = PathImpl.createPathFromString( "annotation" ); - PathImpl middlePath = PathImpl.createPathFromString( "annotation.property" ); - PathImpl middlePath2 = PathImpl.createPathFromString( "annotation.property[2]" ); - PathImpl middlePath3 = PathImpl.createPathFromString( "annotation.property[3]" ); - PathImpl fullPath3 = PathImpl.createPathFromString( "annotation.property[3].element" ); - PathImpl fullPath4 = PathImpl.createPathFromString( "annotation.property[4].element" ); + ModifiablePath subPath = ModifiablePath.createPathFromString( "annotation" ); + ModifiablePath middlePath = ModifiablePath.createPathFromString( "annotation.property" ); + ModifiablePath middlePath2 = ModifiablePath.createPathFromString( "annotation.property[2]" ); + ModifiablePath middlePath3 = ModifiablePath.createPathFromString( "annotation.property[3]" ); + ModifiablePath fullPath3 = ModifiablePath.createPathFromString( "annotation.property[3].element" ); + ModifiablePath fullPath4 = ModifiablePath.createPathFromString( "annotation.property[4].element" ); assertTrue( subPath.isSubPathOf( middlePath ), "bean is subpath of its properties" ); assertFalse( middlePath.isSubPathOf( subPath ), "a property is not a subPath of its bean" ); @@ -186,13 +187,13 @@ public void testIsSubPathOf() { @Test public void testIsSubPathOrContains() { - PathImpl rootPath = PathImpl.createPathFromString( "" ); - PathImpl subPath = PathImpl.createPathFromString( "annotation" ); - PathImpl middlePath = PathImpl.createPathFromString( "annotation.property" ); - PathImpl middlePath2 = PathImpl.createPathFromString( "annotation.property[2]" ); - PathImpl middlePath3 = PathImpl.createPathFromString( "annotation.property[3]" ); - PathImpl fullPath3 = PathImpl.createPathFromString( "annotation.property[3].element" ); - PathImpl fullPath4 = PathImpl.createPathFromString( "annotation.property[4].element" ); + ModifiablePath rootPath = ModifiablePath.createPathFromString( "" ); + ModifiablePath subPath = ModifiablePath.createPathFromString( "annotation" ); + ModifiablePath middlePath = ModifiablePath.createPathFromString( "annotation.property" ); + ModifiablePath middlePath2 = ModifiablePath.createPathFromString( "annotation.property[2]" ); + ModifiablePath middlePath3 = ModifiablePath.createPathFromString( "annotation.property[3]" ); + ModifiablePath fullPath3 = ModifiablePath.createPathFromString( "annotation.property[3].element" ); + ModifiablePath fullPath4 = ModifiablePath.createPathFromString( "annotation.property[4].element" ); assertTrue( rootPath.isSubPathOrContains( middlePath ), "root path is in every path" ); assertTrue( middlePath.isSubPathOrContains( rootPath ), "every path contains the root path" ); @@ -248,14 +249,14 @@ public void testCreationOfExecutablePath() throws Exception { ExecutableMetaData executableMetaData = beanMetaDataManager.getBeanMetaData( Container.class ) .getMetaDataFor( executable ).get(); - PathImpl methodParameterPath = PathImpl.createPathForExecutable( executableMetaData ); + ModifiablePath methodParameterPath = ModifiablePath.createPathForExecutable( executableMetaData ); assertEquals( methodParameterPath.toString(), "addItem" ); } @Test(expectedExceptions = IllegalArgumentException.class) - public void testCreationOfExecutablePathFailsDueToMissingExecutable() throws Exception { - PathImpl.createPathForExecutable( null ); + public void testCreationOfExecutablePathFailsDueToMissingExecutable() { + ModifiablePath.createPathForExecutable( null ); } class Container { diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/serialization/CustomConstraintSerializableTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/serialization/CustomConstraintSerializableTest.java index 9698f0d15b..f7c8200b15 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/serialization/CustomConstraintSerializableTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/serialization/CustomConstraintSerializableTest.java @@ -6,6 +6,7 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; +import java.io.Serial; import java.io.Serializable; import java.util.Set; @@ -57,6 +58,7 @@ public static byte[] doSerialize(Object obj) throws Exception { } static class CustomEmail implements Serializable { + @Serial private static final long serialVersionUID = -9095271389455131159L; @Email diff --git a/engine/src/test/java/org/hibernate/validator/testutils/ValidatorUtil.java b/engine/src/test/java/org/hibernate/validator/testutils/ValidatorUtil.java index d26f2a712e..76a10e9a3a 100644 --- a/engine/src/test/java/org/hibernate/validator/testutils/ValidatorUtil.java +++ b/engine/src/test/java/org/hibernate/validator/testutils/ValidatorUtil.java @@ -26,7 +26,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext; import org.hibernate.validator.internal.engine.DefaultClockProvider; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl; -import org.hibernate.validator.internal.engine.path.PathImpl; +import org.hibernate.validator.internal.engine.path.ModifiablePath; import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel; import org.hibernate.validator.testutil.DummyTraversableResolver; import org.hibernate.validator.testutil.ValidationInvocationHandler; @@ -235,10 +235,10 @@ public static T getValidatingProxy(I implementor, Validator exe } public static HibernateConstraintValidatorContext getConstraintValidatorContext() { - return getConstraintValidatorContext( PathImpl.createRootPath() ); + return getConstraintValidatorContext( ModifiablePath.createRootPath() ); } - public static HibernateConstraintValidatorContext getConstraintValidatorContext(PathImpl propertyPath) { + public static HibernateConstraintValidatorContext getConstraintValidatorContext(ModifiablePath propertyPath) { return new ConstraintValidatorContextImpl( DefaultClockProvider.INSTANCE, propertyPath, null, null, ExpressionLanguageFeatureLevel.BEAN_PROPERTIES, ExpressionLanguageFeatureLevel.NONE ); diff --git a/performance/src/main/jakarta/org/hibernate/validator/performance/simple/SimpleSingleElementValidation.java b/performance/src/main/jakarta/org/hibernate/validator/performance/simple/SimpleSingleElementValidation.java new file mode 100644 index 0000000000..c5e4e313e6 --- /dev/null +++ b/performance/src/main/jakarta/org/hibernate/validator/performance/simple/SimpleSingleElementValidation.java @@ -0,0 +1,85 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.validator.performance.simple; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +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.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Benchmark) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(value = 1) +@Threads(50) +@Warmup(iterations = 10) +@Measurement(iterations = 20) +public class SimpleSingleElementValidation { + + // The class to be validated + private static class User { + @NotNull + @Size(min = 3, max = 50) + private String name; + + @Size(min = 3, max = 50) + @NotNull + private String email; + + @Min(value = 20) + private int age; + + public User(String name, String email, int age) { + this.name = name; + this.email = email; + this.age = age; + } + } + + @State(Scope.Benchmark) + public static class BenchmarkState { + Validator validator; + User validUser; + User invalidUser; + + @Setup + public void setup() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + this.validator = factory.getValidator(); + this.validUser = new User( "John Doe", "john.doe@example.com", 25 ); + this.invalidUser = new User( "Jo", "invalid-email", 19 ); + } + } + + @Benchmark + public void validObjectValidation(BenchmarkState state, Blackhole blackhole) { + Set> violations = state.validator.validate( state.validUser ); + blackhole.consume( violations ); + } + + @Benchmark + public void invalidObjectValidation(BenchmarkState state, Blackhole blackhole) { + Set> violations = state.validator.validate( state.invalidUser ); + blackhole.consume( violations ); + } +}