Skip to content

Commit 3c57d55

Browse files
committed
Polish contribution
- Split Validator::of into two factory methods: - forInstanceOf using Class::isAssignableFrom - forType using Class::equals - Moved anonymous implementation into TypedValidator with default access Closes gh-30341
1 parent 5f98afc commit 3c57d55

File tree

5 files changed

+176
-58
lines changed

5 files changed

+176
-58
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.validation;
18+
19+
import java.util.function.BiConsumer;
20+
import java.util.function.Predicate;
21+
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* Validator instance returned by {@link Validator#forInstanceOf(Class, BiConsumer)}
26+
* and {@link Validator#forType(Class, BiConsumer)}.
27+
*
28+
* @author Toshiaki Maki
29+
* @author Arjen Poutsma
30+
* @since 6.1
31+
* @param <T> the target object type
32+
*/
33+
final class TypedValidator<T> implements Validator {
34+
35+
private final Class<T> targetClass;
36+
37+
private final Predicate<Class<?>> supports;
38+
39+
private final BiConsumer<T, Errors> validate;
40+
41+
42+
public TypedValidator(Class<T> targetClass, Predicate<Class<?>> supports, BiConsumer<T, Errors> validate) {
43+
Assert.notNull(targetClass, "TargetClass must not be null");
44+
Assert.notNull(supports, "Supports function must not be null");
45+
Assert.notNull(validate, "Validate function must not be null");
46+
47+
this.targetClass = targetClass;
48+
this.supports = supports;
49+
this.validate = validate;
50+
}
51+
52+
53+
@Override
54+
public boolean supports(Class<?> clazz) {
55+
return this.supports.test(clazz);
56+
}
57+
58+
@Override
59+
public void validate(Object target, Errors errors) {
60+
this.validate.accept(this.targetClass.cast(target), errors);
61+
}
62+
63+
}

spring-context/src/main/java/org/springframework/validation/Validator.java

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
import java.util.function.BiConsumer;
2020

21-
import org.springframework.util.Assert;
22-
2321
/**
2422
* A validator for application-specific objects.
2523
*
@@ -30,40 +28,33 @@
3028
* of an application, and supports the encapsulation of validation
3129
* logic as a first-class citizen in its own right.
3230
*
33-
* <p>Find below a simple but complete {@code Validator}
34-
* implementation, which validates that the various {@link String}
35-
* properties of a {@code UserLogin} instance are not empty
36-
* (that is they are not {@code null} and do not consist
31+
* <p>Implementations can be created via the static factory methods
32+
* {@link #forInstanceOf(Class, BiConsumer)} or
33+
* {@link #forType(Class, BiConsumer)}.
34+
* Below is a simple but complete {@code Validator} that validates that the
35+
* various {@link String} properties of a {@code UserLogin} instance are not
36+
* empty (they are not {@code null} and do not consist
3737
* wholly of whitespace), and that any password that is present is
3838
* at least {@code 'MINIMUM_PASSWORD_LENGTH'} characters in length.
3939
*
40-
* <pre class="code">public class UserLoginValidator implements Validator {
41-
*
42-
* private static final int MINIMUM_PASSWORD_LENGTH = 6;
43-
*
44-
* public boolean supports(Class clazz) {
45-
* return UserLogin.class.isAssignableFrom(clazz);
46-
* }
47-
*
48-
* public void validate(Object target, Errors errors) {
49-
* ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
50-
* ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
51-
* UserLogin login = (UserLogin) target;
52-
* if (login.getPassword() != null
53-
* &amp;&amp; login.getPassword().trim().length() &lt; MINIMUM_PASSWORD_LENGTH) {
54-
* errors.rejectValue("password", "field.min.length",
55-
* new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
56-
* "The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
57-
* }
58-
* }
59-
* }</pre>
40+
* <pre class="code">Validator userLoginValidator = Validator.forInstance(UserLogin.class, (login, errors) -> {
41+
* ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
42+
* ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
43+
* if (login.getPassword() != null
44+
* &amp;&amp; login.getPassword().trim().length() &lt; MINIMUM_PASSWORD_LENGTH) {
45+
* errors.rejectValue("password", "field.min.length",
46+
* new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
47+
* "The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
48+
* }
49+
* });</pre>
6050
*
6151
* <p>See also the Spring reference manual for a fuller discussion of
6252
* the {@code Validator} interface and its role in an enterprise
6353
* application.
6454
*
6555
* @author Rod Johnson
6656
* @author Toshiaki Maki
57+
* @author Arjen Poutsma
6758
* @see SmartValidator
6859
* @see Errors
6960
* @see ValidationUtils
@@ -97,38 +88,54 @@ public interface Validator {
9788
*/
9889
void validate(Object target, Errors errors);
9990

91+
10092
/**
101-
* Takes the {@link BiConsumer} containing the validation logic for the specific type
102-
* <code>&lt;T&gt;</code> and returns the {@link Validator} instance.<br>
103-
* This validator implements the <i>typical</i> {@link #supports(Class)} method
104-
* for the given <code>&lt;T&gt;</code>.<br>
105-
*
106-
* By using this method, a {@link Validator} can be implemented as follows:
93+
* Return a {@code Validator} that checks whether the target object
94+
* {@linkplain Class#isAssignableFrom(Class) is an instance of}
95+
* {@code targetClass}, resorting to {@code delegate} to populate
96+
* {@link Errors} if it is.
10797
*
108-
* <pre class="code">Validator passwordEqualsValidator = Validator.of(PasswordResetForm.class, (form, errors) -> {
109-
* if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
110-
* errors.rejectValue("confirmPassword",
111-
* "PasswordEqualsValidator.passwordResetForm.password",
112-
* "password and confirm password must be same.");
113-
* }
114-
* });</pre>
115-
* @param targetClass the class of the object that is to be validated
116-
* @param delegate the validation logic to delegate for the specific type <code>&lt;T&gt;</code>
117-
* @param <T> the type of the object that is to be validated
118-
* @return the {@link Validator} instance
98+
* <p>For instance:
99+
* <pre class="code">Validator passwordEqualsValidator = Validator.forInstanceOf(PasswordResetForm.class, (form, errors) -> {
100+
* if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
101+
* errors.rejectValue("confirmPassword",
102+
* "PasswordEqualsValidator.passwordResetForm.password",
103+
* "password and confirm password must be same.");
104+
* }
105+
* });</pre>
106+
* @param targetClass the class supported by the returned validator
107+
* @param delegate function invoked with the target object, if it is an
108+
* instance of type T
109+
* @param <T> the target object type
110+
* @return the created {@code Validator}
111+
* @since 6.1
119112
*/
120-
static <T> Validator of(Class<T> targetClass, BiConsumer<T, Errors> delegate) {
121-
Assert.notNull(targetClass, "'targetClass' must not be null.");
122-
return new Validator() {
123-
@Override
124-
public boolean supports(Class<?> clazz) {
125-
return targetClass.isAssignableFrom(clazz);
126-
}
113+
static <T> Validator forInstanceOf(Class<T> targetClass, BiConsumer<T, Errors> delegate) {
114+
return new TypedValidator<>(targetClass, targetClass::isAssignableFrom, delegate);
115+
}
127116

128-
@Override
129-
public void validate(Object target, Errors errors) {
130-
delegate.accept(targetClass.cast(target), errors);
131-
}
132-
};
117+
/**
118+
* Return a {@code Validator} that checks whether the target object's class
119+
* is identical to {@code targetClass}, resorting to {@code delegate} to
120+
* populate {@link Errors} if it is.
121+
*
122+
* <p>For instance:
123+
* <pre class="code">Validator passwordEqualsValidator = Validator.forType(PasswordResetForm.class, (form, errors) -> {
124+
* if (!Objects.equals(form.getPassword(), form.getConfirmPassword())) {
125+
* errors.rejectValue("confirmPassword",
126+
* "PasswordEqualsValidator.passwordResetForm.password",
127+
* "password and confirm password must be same.");
128+
* }
129+
* });</pre>
130+
* @param targetClass the exact class supported by the returned validator (no subclasses)
131+
* @param delegate function invoked with the target object, if it is an
132+
* instance of type T
133+
* @param <T> the target object type
134+
* @return the created {@code Validator}
135+
* @since 6.1
136+
*/
137+
static <T> Validator forType(Class<T> targetClass, BiConsumer<T, Errors> delegate) {
138+
return new TypedValidator<>(targetClass, targetClass::equals, delegate);
133139
}
140+
134141
}

spring-context/src/test/java/org/springframework/validation/DataBinderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
*/
8383
class DataBinderTests {
8484

85-
Validator spouseValidator = Validator.of(TestBean.class, (tb, errors) -> {
85+
private final Validator spouseValidator = Validator.forInstanceOf(TestBean.class, (tb, errors) -> {
8686
if (tb == null || "XXX".equals(tb.getName())) {
8787
errors.rejectValue("", "SPOUSE_NOT_AVAILABLE");
8888
return;

spring-context/src/test/java/org/springframework/validation/ValidationUtilsTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
*/
3434
public class ValidationUtilsTests {
3535

36-
Validator emptyValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!"));
36+
private final Validator emptyValidator = Validator.forInstanceOf(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmpty(errors, "name", "EMPTY", "You must enter a name!"));
3737

38-
Validator emptyOrWhitespaceValidator = Validator.of(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!"));
38+
private final Validator emptyOrWhitespaceValidator = Validator.forInstanceOf(TestBean.class, (testBean, errors) -> ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "EMPTY_OR_WHITESPACE", "You must enter a name!"));
3939

4040
@Test
4141
public void testInvokeValidatorWithNullValidator() throws Exception {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.validation;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.testfixture.beans.TestBean;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
/**
26+
* @author Arjen Poutsma
27+
*/
28+
class ValidatorTests {
29+
30+
@Test
31+
public void testSupportsForInstanceOf() {
32+
Validator validator = Validator.forInstanceOf(TestBean.class, (testBean, errors) -> {});
33+
assertThat(validator.supports(TestBean.class)).isTrue();
34+
assertThat(validator.supports(TestBeanSubclass.class)).isTrue();
35+
}
36+
37+
@Test
38+
public void testSupportsForType() {
39+
Validator validator = Validator.forType(TestBean.class, (testBean, errors) -> {});
40+
assertThat(validator.supports(TestBean.class)).isTrue();
41+
assertThat(validator.supports(TestBeanSubclass.class)).isFalse();
42+
}
43+
44+
45+
private static class TestBeanSubclass extends TestBean {
46+
}
47+
48+
}

0 commit comments

Comments
 (0)