diff --git a/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java index a7ee446e..cb12403d 100644 --- a/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java +++ b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java @@ -4,6 +4,7 @@ import jakarta.validation.ConstraintValidatorContext; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** @@ -20,39 +21,61 @@ public void initialize(HasSerializedDataTypeConstant annotation) { @Override public boolean isValid(Object obj, ConstraintValidatorContext context) { + final Result result = analyze(obj.getClass(), name); + if (result.message() == null) { + return true; + } + error(context, result.message()); + return false; + } + + private void error(ConstraintValidatorContext context, String message) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(message).addConstraintViolation(); + } + + private static Result analyze(final Class clasz, final String name) { try { - final Field field = obj.getClass().getField(name); + final Field field = clasz.getField(name); final int modifiers = field.getModifiers(); if (!Modifier.isStatic(modifiers)) { - error(context, "Field '" + name + "' is not static (#1)"); - return false; + return new Result("Field '" + name + "' is not static (#1)", null); } if (field.getType() != SerializedDataType.class) { - error(context, "Expected constant '" + name + "' to be of type '" + SerializedDataType.class.getName() + "', but was: " + field.getType().getName() + " (#3)"); - return false; + return new Result("Expected constant '" + name + "' to be of type '" + SerializedDataType.class.getName() + "', but was: " + field.getType().getName() + " (#3)", null); } - final Object value = field.get(obj); + final Object value = field.get(clasz); if (value == null) { - error(context, "Constant '" + name + "' is expected to be a non-null value (#4)"); - return false; + return new Result("Constant '" + name + "' is expected to be a non-null value (#4)", null); } if (!Modifier.isFinal(modifiers)) { - error(context, "Constant '" + name + "' is not not final (#5)"); - return false; + return new Result("Constant '" + name + "' is not not final (#5)", null); } - return true; + return new Result(null, value); } catch (final NoSuchFieldException ex) { - error(context, "The field '" + name + "' is undefined or it is not public (#2)"); - return false; + return new Result("The field '" + name + "' is undefined or it is not public (#2)", null); } catch (final IllegalAccessException ex) { throw new IllegalStateException("Failed to execute method", ex); } + } + private record Result(String message, Object value) { } - private void error(ConstraintValidatorContext context, String message) { - context.disableDefaultConstraintViolation(); - context.buildConstraintViolationWithTemplate(message).addConstraintViolation(); + /** + * Returns a constant of type {@link SerializedDataType} in a class. Throws an {@link IllegalArgumentException} + * in case there is a problem with the field. + * + * @param clasz Class to inspect. + * @param fieldName Name of the public static field of type {@link SerializedDataType}. + * @return Value of the constant. + */ + public static SerializedDataType extractValue(final Class clasz, final String fieldName) { + final Result result = analyze(clasz, fieldName); + if (result.message() == null) { + return (SerializedDataType) result.value(); + } + throw new IllegalArgumentException(result.message()); } } diff --git a/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java b/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java index 191b1dfd..fad650a9 100644 --- a/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java +++ b/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java @@ -27,9 +27,11 @@ import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public final class HasSerializedDataTypeConstantValidatorTest { + public static final String FIELD_NAME = "SER_TYPE"; private static Validator validator; @BeforeAll @@ -42,36 +44,80 @@ static void beforeAll() { @Test public final void testValid() { assertThat(validator.validate(new MyClassValid())).isEmpty(); + assertThat(HasSerializedDataTypeConstantValidator + .extractValue(MyClassValid.class, FIELD_NAME)).isEqualTo(new SerializedDataType("XYZ")); } @Test public final void testNotStatic() { + assertThat(first(validator.validate(new MyClassNotStatic()))).contains("#1"); + + assertThatThrownBy( + () -> HasSerializedDataTypeConstantValidator.extractValue(MyClassNotStatic.class, FIELD_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("#1"); + } @Test public final void testNotPublic() { + assertThat(first(validator.validate(new MyClassNotPublic()))).contains("#2"); + + assertThatThrownBy( + () -> HasSerializedDataTypeConstantValidator.extractValue(MyClassNotPublic.class, FIELD_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("#2"); + } @Test public final void testWrongReturnType() { + assertThat(first(validator.validate(new MyClassWrongType()))).contains("#3"); + + assertThatThrownBy( + () -> HasSerializedDataTypeConstantValidator.extractValue(MyClassWrongType.class, FIELD_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("#3"); + } @Test public final void testWrongReturn() { + assertThat(first(validator.validate(new MyClassNullValue()))).contains("#4"); + + assertThatThrownBy( + () -> HasSerializedDataTypeConstantValidator.extractValue(MyClassNullValue.class, FIELD_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("#4"); + } @Test - public final void testNoMethod() { + public final void testNoField() { + assertThat(first(validator.validate(new MyClassNoField()))).contains("#2"); + + assertThatThrownBy( + () -> HasSerializedDataTypeConstantValidator.extractValue(MyClassNoField.class, FIELD_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("#2"); + } @Test public final void testNotFinal() { + assertThat(first(validator.validate(new MyClassNotFinal()))).contains("#5"); + + assertThatThrownBy( + () -> HasSerializedDataTypeConstantValidator.extractValue(MyClassNotFinal.class, FIELD_NAME)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("#5"); + } private static String first(Set violations) {