Skip to content

Commit 25729f0

Browse files
committed
HV-2127 Do not create unnecessary lists when a simple ConstraintTree is used
which would be the case with most constraints Signed-off-by: marko-bekhta <[email protected]>
1 parent 747cebe commit 25729f0

File tree

5 files changed

+148
-39
lines changed

5 files changed

+148
-39
lines changed

engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ComposingConstraintTree.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import java.util.ArrayList;
1515
import java.util.Collection;
1616
import java.util.List;
17-
import java.util.Optional;
1817
import java.util.stream.Collectors;
1918

2019
import jakarta.validation.ConstraintValidator;
@@ -38,7 +37,7 @@
3837
* @author Guillaume Smet
3938
* @author Marko Bekhta
4039
*/
41-
class ComposingConstraintTree<B extends Annotation> extends ConstraintTree<B> {
40+
final class ComposingConstraintTree<B extends Annotation> extends ConstraintTree<B> {
4241

4342
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
4443

@@ -61,6 +60,23 @@ private <U extends Annotation> ConstraintTree<U> createConstraintTree(Constraint
6160
}
6261
}
6362

63+
@Override
64+
public boolean validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext) {
65+
List<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts = new ArrayList<>( 5 );
66+
validateConstraints( validationContext, valueContext, violatedConstraintValidatorContexts );
67+
if ( !violatedConstraintValidatorContexts.isEmpty() ) {
68+
for ( ConstraintValidatorContextImpl constraintValidatorContext : violatedConstraintValidatorContexts ) {
69+
for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) {
70+
validationContext.addConstraintFailure(
71+
valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor()
72+
);
73+
}
74+
}
75+
return false;
76+
}
77+
return true;
78+
}
79+
6480
@Override
6581
protected void validateConstraints(ValidationContext<?> validationContext,
6682
ValueContext<?, ?> valueContext,
@@ -69,7 +85,7 @@ protected void validateConstraints(ValidationContext<?> validationContext,
6985
validationContext, valueContext, violatedConstraintValidatorContexts
7086
);
7187

72-
Optional<ConstraintValidatorContextImpl> violatedLocalConstraintValidatorContext;
88+
ConstraintValidatorContextImpl violatedLocalConstraintValidatorContext;
7389

7490
// After all children are validated the actual ConstraintValidator of the constraint itself is executed
7591
if ( mainConstraintNeedsEvaluation( validationContext, violatedConstraintValidatorContexts ) ) {
@@ -103,15 +119,15 @@ protected void validateConstraints(ValidationContext<?> validationContext,
103119

104120
// We re-evaluate the boolean composition by taking into consideration also the violations
105121
// from the local constraintValidator
106-
if ( !violatedLocalConstraintValidatorContext.isPresent() ) {
122+
if ( violatedLocalConstraintValidatorContext == null ) {
107123
compositionResult.setAtLeastOneTrue( true );
108124
}
109125
else {
110126
compositionResult.setAllTrue( false );
111127
}
112128
}
113129
else {
114-
violatedLocalConstraintValidatorContext = Optional.empty();
130+
violatedLocalConstraintValidatorContext = null;
115131
}
116132

117133
if ( !passesCompositionTypeRequirement( violatedConstraintValidatorContexts, compositionResult ) ) {
@@ -157,7 +173,7 @@ private boolean mainConstraintNeedsEvaluation(ValidationContext<?> validationCon
157173
private void prepareFinalConstraintViolations(ValidationContext<?> validationContext,
158174
ValueContext<?, ?> valueContext,
159175
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts,
160-
Optional<ConstraintValidatorContextImpl> localConstraintValidatorContext) {
176+
ConstraintValidatorContextImpl localConstraintValidatorContext) {
161177
if ( reportAsSingleViolation() ) {
162178
// We clear the current violations list anyway
163179
violatedConstraintValidatorContexts.clear();
@@ -166,7 +182,7 @@ private void prepareFinalConstraintViolations(ValidationContext<?> validationCon
166182
// violations or not (or if there is no local ConstraintValidator at all).
167183
// If not we create a violation
168184
// using the error message in the annotation declaration at top level.
169-
if ( !localConstraintValidatorContext.isPresent() ) {
185+
if ( localConstraintValidatorContext == null ) {
170186
violatedConstraintValidatorContexts.add(
171187
validationContext.createConstraintValidatorContextFor(
172188
descriptor, valueContext.getPropertyPath()
@@ -183,8 +199,8 @@ private void prepareFinalConstraintViolations(ValidationContext<?> validationCon
183199
// as checked in test CustomErrorMessage.java
184200
// If no violations have been reported from the local ConstraintValidator, or no such validator exists,
185201
// then we just add an empty list.
186-
if ( localConstraintValidatorContext.isPresent() ) {
187-
violatedConstraintValidatorContexts.add( localConstraintValidatorContext.get() );
202+
if ( localConstraintValidatorContext != null ) {
203+
violatedConstraintValidatorContexts.add( localConstraintValidatorContext );
188204
}
189205
}
190206

engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import java.lang.annotation.Annotation;
88
import java.lang.invoke.MethodHandles;
99
import java.lang.reflect.Type;
10-
import java.util.ArrayList;
1110
import java.util.Collection;
12-
import java.util.List;
1311
import java.util.Optional;
1412

1513
import jakarta.validation.ConstraintDeclarationException;
@@ -33,7 +31,7 @@
3331
* @author Guillaume Smet
3432
* @author Marko Bekhta
3533
*/
36-
public abstract class ConstraintTree<A extends Annotation> {
34+
public abstract sealed class ConstraintTree<A extends Annotation> permits SimpleConstraintTree, ComposingConstraintTree {
3735

3836
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
3937

@@ -68,21 +66,7 @@ public static <U extends Annotation> ConstraintTree<U> of(ConstraintValidatorMan
6866
}
6967
}
7068

71-
public final boolean validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext) {
72-
List<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts = new ArrayList<>( 5 );
73-
validateConstraints( validationContext, valueContext, violatedConstraintValidatorContexts );
74-
if ( !violatedConstraintValidatorContexts.isEmpty() ) {
75-
for ( ConstraintValidatorContextImpl constraintValidatorContext : violatedConstraintValidatorContexts ) {
76-
for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) {
77-
validationContext.addConstraintFailure(
78-
valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor()
79-
);
80-
}
81-
}
82-
return false;
83-
}
84-
return true;
85-
}
69+
public abstract boolean validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext);
8670

8771
protected abstract void validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext,
8872
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts);
@@ -168,7 +152,7 @@ private ValidationException getExceptionForNullValidator(Type validatedValueType
168152
* @return an {@link Optional#empty()} if there is no violation or a corresponding {@link ConstraintValidatorContextImpl}
169153
* otherwise.
170154
*/
171-
protected final <V> Optional<ConstraintValidatorContextImpl> validateSingleConstraint(
155+
protected final <V> ConstraintValidatorContextImpl validateSingleConstraint(
172156
ValueContext<?, ?> valueContext,
173157
ConstraintValidatorContextImpl constraintValidatorContext,
174158
ConstraintValidator<A, V> validator) {
@@ -187,9 +171,9 @@ protected final <V> Optional<ConstraintValidatorContextImpl> validateSingleConst
187171
if ( !isValid ) {
188172
//We do not add these violations yet, since we don't know how they are
189173
//going to influence the final boolean evaluation
190-
return Optional.of( constraintValidatorContext );
174+
return constraintValidatorContext;
191175
}
192-
return Optional.empty();
176+
return null;
193177
}
194178

195179
@Override

engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/SimpleConstraintTree.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* @author Guillaume Smet
2828
* @author Marko Bekhta
2929
*/
30-
class SimpleConstraintTree<B extends Annotation> extends ConstraintTree<B> {
30+
final class SimpleConstraintTree<B extends Annotation> extends ConstraintTree<B> {
3131

3232
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
3333

@@ -36,16 +36,43 @@ public SimpleConstraintTree(ConstraintValidatorManager constraintValidatorManage
3636
}
3737

3838
@Override
39-
protected void validateConstraints(ValidationContext<?> validationContext,
39+
public boolean validateConstraints(ValidationContext<?> validationContext, ValueContext<?, ?> valueContext) {
40+
ConstraintValidatorContextImpl constraintValidatorContext = doValidateConstraints( validationContext, valueContext );
41+
if ( constraintValidatorContext != null ) {
42+
for ( ConstraintViolationCreationContext constraintViolationCreationContext : constraintValidatorContext.getConstraintViolationCreationContexts() ) {
43+
validationContext.addConstraintFailure(
44+
valueContext, constraintViolationCreationContext, constraintValidatorContext.getConstraintDescriptor()
45+
);
46+
}
47+
return false;
48+
}
49+
return true;
50+
}
51+
52+
@Override
53+
protected void validateConstraints(
54+
ValidationContext<?> validationContext,
4055
ValueContext<?, ?> valueContext,
41-
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
56+
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts
57+
) {
58+
ConstraintValidatorContextImpl constraintValidatorContext = doValidateConstraints( validationContext, valueContext );
4259

60+
if ( constraintValidatorContext != null ) {
61+
violatedConstraintValidatorContexts.add( constraintValidatorContext );
62+
}
63+
}
64+
65+
private ConstraintValidatorContextImpl doValidateConstraints(
66+
ValidationContext<?> validationContext,
67+
ValueContext<?, ?> valueContext
68+
) {
4369
if ( LOG.isTraceEnabled() ) {
4470
if ( validationContext.isShowValidatedValuesInTraceLogs() ) {
4571
LOG.tracef(
4672
"Validating value %s against constraint defined by %s.",
4773
valueContext.getCurrentValidatedValue(),
48-
descriptor );
74+
descriptor
75+
);
4976
}
5077
else {
5178
LOG.tracef( "Validating against constraint defined by %s.", descriptor );
@@ -61,8 +88,6 @@ protected void validateConstraints(ValidationContext<?> validationContext,
6188
);
6289

6390
// validate
64-
if ( validateSingleConstraint( valueContext, constraintValidatorContext, validator ).isPresent() ) {
65-
violatedConstraintValidatorContexts.add( constraintValidatorContext );
66-
}
91+
return validateSingleConstraint( valueContext, constraintValidatorContext, validator );
6792
}
6893
}

engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraint.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,8 @@ public boolean validateConstraint(ValidationContext<?> validationContext, ValueC
128128

129129
private boolean doValidateConstraint(ValidationContext<?> executionContext, ValueContext<?, ?> valueContext) {
130130
valueContext.setConstraintLocationKind( getConstraintLocationKind() );
131-
boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext );
132131

133-
return validationResult;
132+
return constraintTree.validateConstraints( executionContext, valueContext );
134133
}
135134

136135
public ConstraintLocation getLocation() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.validator.performance.simple;
6+
7+
import java.util.Set;
8+
import java.util.concurrent.TimeUnit;
9+
10+
import jakarta.validation.ConstraintViolation;
11+
import jakarta.validation.Validation;
12+
import jakarta.validation.Validator;
13+
import jakarta.validation.ValidatorFactory;
14+
import jakarta.validation.constraints.Min;
15+
import jakarta.validation.constraints.NotNull;
16+
import jakarta.validation.constraints.Size;
17+
import org.openjdk.jmh.annotations.Benchmark;
18+
import org.openjdk.jmh.annotations.BenchmarkMode;
19+
import org.openjdk.jmh.annotations.Fork;
20+
import org.openjdk.jmh.annotations.Measurement;
21+
import org.openjdk.jmh.annotations.Mode;
22+
import org.openjdk.jmh.annotations.OutputTimeUnit;
23+
import org.openjdk.jmh.annotations.Scope;
24+
import org.openjdk.jmh.annotations.Setup;
25+
import org.openjdk.jmh.annotations.State;
26+
import org.openjdk.jmh.annotations.Threads;
27+
import org.openjdk.jmh.annotations.Warmup;
28+
import org.openjdk.jmh.infra.Blackhole;
29+
30+
@State(Scope.Benchmark)
31+
@BenchmarkMode(Mode.Throughput)
32+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
33+
@Fork(value = 1)
34+
@Threads(50)
35+
@Warmup(iterations = 10)
36+
@Measurement(iterations = 20)
37+
public class SimpleSingleElementValidation {
38+
39+
// The class to be validated
40+
private static class User {
41+
@NotNull
42+
@Size(min = 3, max = 50)
43+
private String name;
44+
45+
@Size(min = 3, max = 50)
46+
@NotNull
47+
private String email;
48+
49+
@Min(value = 20)
50+
private int age;
51+
52+
public User(String name, String email, int age) {
53+
this.name = name;
54+
this.email = email;
55+
this.age = age;
56+
}
57+
}
58+
59+
@State(Scope.Benchmark)
60+
public static class BenchmarkState {
61+
Validator validator;
62+
User validUser;
63+
User invalidUser;
64+
65+
@Setup
66+
public void setup() {
67+
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
68+
this.validator = factory.getValidator();
69+
this.validUser = new User( "John Doe", "[email protected]", 25 );
70+
this.invalidUser = new User( "Jo", "invalid-email", 19 );
71+
}
72+
}
73+
74+
@Benchmark
75+
public void validObjectValidation(BenchmarkState state, Blackhole blackhole) {
76+
Set<ConstraintViolation<User>> violations = state.validator.validate( state.validUser );
77+
blackhole.consume( violations );
78+
}
79+
80+
@Benchmark
81+
public void invalidObjectValidation(BenchmarkState state, Blackhole blackhole) {
82+
Set<ConstraintViolation<User>> violations = state.validator.validate( state.invalidUser );
83+
blackhole.consume( violations );
84+
}
85+
}

0 commit comments

Comments
 (0)