Skip to content

Commit

Permalink
Added new annotation to locate constant field
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-schnell committed Jan 14, 2024
1 parent df8317c commit 9983066
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 1 deletion.
6 changes: 6 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.fuin.esc.api;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

/**
Expand All @@ -10,7 +13,8 @@
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SerializedDataTypeConstant {
@Constraint(validatedBy = { HasSerializedDataTypeConstantValidator.class })
public @interface HasSerializedDataTypeConstant {

/**
* Returns the name of a public static constant in the annotated class.
Expand All @@ -19,4 +23,10 @@
*/
String value() default "SER_TYPE";

String message() default "Does not define a public static constant with the given name";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.fuin.esc.api;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
* Determines if the annotated class has a public static constant with the given name and {@link SerializedDataType} type.
*/
public class HasSerializedDataTypeConstantValidator implements ConstraintValidator<HasSerializedDataTypeConstant, Object> {

private String name;

@Override
public void initialize(HasSerializedDataTypeConstant annotation) {
this.name = annotation.value();
}

@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
try {
final Field field = obj.getClass().getField(name);
final int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers)) {
error(context, "Field '" + name + "' is not static (#1)");
return false;
}
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;
}
final Object value = field.get(obj);
if (value == null) {
error(context, "Constant '" + name + "' is expected to be a non-null value (#4)");
return false;
}
if (!Modifier.isFinal(modifiers)) {
error(context, "Constant '" + name + "' is not not final (#5)");
return false;
}
return true;
} catch (final NoSuchFieldException ex) {
error(context, "The field '" + name + "' is undefined or it is not public (#2)");
return false;
} catch (final IllegalAccessException ex) {
throw new IllegalStateException("Failed to execute method", ex);
}

}

private void error(ConstraintValidatorContext context, String message) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Copyright (C) 2013 Future Invent Informationsmanagement GmbH. All rights
* reserved. <http://www.fuin.org/>
* <p>
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option) any
* later version.
* <p>
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
* <p>
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fuin.esc.api;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

public final class HasSerializedDataTypeConstantValidatorTest {

private static Validator validator;

@BeforeAll
static void beforeAll() {
try (final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
validator = validatorFactory.getValidator();
}
}

@Test
public final void testValid() {
assertThat(validator.validate(new MyClassValid())).isEmpty();
}

@Test
public final void testNotStatic() {
assertThat(first(validator.validate(new MyClassNotStatic()))).contains("#1");
}

@Test
public final void testNotPublic() {
assertThat(first(validator.validate(new MyClassNotPublic()))).contains("#2");
}

@Test
public final void testWrongReturnType() {
assertThat(first(validator.validate(new MyClassWrongType()))).contains("#3");
}

@Test
public final void testWrongReturn() {
assertThat(first(validator.validate(new MyClassNullValue()))).contains("#4");
}

@Test
public final void testNoMethod() {
assertThat(first(validator.validate(new MyClassNoField()))).contains("#2");
}

@Test
public final void testNotFinal() {
assertThat(first(validator.validate(new MyClassNotFinal()))).contains("#5");
}

private static String first(Set<?> violations) {
return violations.stream().map(v -> ((ConstraintViolation<?>) v).getMessage()).findFirst().orElse(null);
}

@HasSerializedDataTypeConstant
public static final class MyClassValid {
public static final SerializedDataType SER_TYPE = new SerializedDataType("XYZ");
}

@HasSerializedDataTypeConstant
public static final class MyClassNotStatic {
public final SerializedDataType SER_TYPE = new SerializedDataType("XYZ");
}

@HasSerializedDataTypeConstant
public static final class MyClassNotPublic {
protected static final SerializedDataType SER_TYPE = new SerializedDataType("XYZ");
}

@HasSerializedDataTypeConstant
public static final class MyClassNoField {
}

@HasSerializedDataTypeConstant
public static final class MyClassWrongType {
public static final Integer SER_TYPE = 123;
}

@HasSerializedDataTypeConstant
public static final class MyClassNullValue {
public static final SerializedDataType SER_TYPE = null;
}

@HasSerializedDataTypeConstant
public static final class MyClassNotFinal {
public static SerializedDataType SER_TYPE = new SerializedDataType("XYZ");
}


}

0 comments on commit 9983066

Please sign in to comment.