Skip to content

Commit

Permalink
Added new static method annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-schnell committed Jan 14, 2024
1 parent c3acb36 commit 3ec4fa3
Show file tree
Hide file tree
Showing 17 changed files with 459 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.fuin.objects4j.common;

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

import java.lang.annotation.*;

/**
Expand All @@ -10,8 +13,9 @@
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(IsValidCapables.class)
public @interface IsValidCapable {
@Repeatable(HasPublicStaticIsValidMethods.class)
@Constraint(validatedBy = { HasPublicStaticIsValidMethodValidator.class })
public @interface HasPublicStaticIsValidMethod {

/**
* Returns the name of a public static method in the annotated class.<br>
Expand All @@ -34,4 +38,10 @@
*/
Class<?> param() default String.class;

String message() default "Does not define a public static method with the given argument type and returns boolean";

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.fuin.objects4j.common;

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
* Determines if the annotated class has:
* - a public static method with the given name
* - a parameter with the given type
* defined in the annotation and the return type of that method is {@literal boolean}.
*/
public class HasPublicStaticIsValidMethodValidator implements ConstraintValidator<HasPublicStaticIsValidMethod, Object> {

private String methodName;

private Class<?> paramClass;

@Override
public void initialize(HasPublicStaticIsValidMethod annotation) {
this.methodName = annotation.method();
this.paramClass = annotation.param();
}

@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
try {
final Method method = obj.getClass().getMethod(methodName, paramClass);
final int modifiers = method.getModifiers();
if (!Modifier.isStatic(modifiers)) {
error(context, "Method '" + methodName + "' is not static (#1)");
return false;
}
if (method.getReturnType() != boolean.class) {
error(context, "Method '" + methodName + "' does not return 'boolean', but: " + method.getReturnType().getName() + " (#3)");
return false;
}
final boolean valid = (boolean) method.invoke(obj, (Object) null);
if (!valid) {
error(context, "Method '" + methodName + "' is expected to return 'true' on 'null' argument, but was 'false' (#4)");
return false;
}
return true;
} catch (final NoSuchMethodException ex) {
error(context, "The method '" + methodName + "' is undefined or it is not public (#2)");
return false;
} catch (final IllegalAccessException | InvocationTargetException 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,21 @@
package org.fuin.objects4j.common;

import java.lang.annotation.*;

/**
* Collection of {@link HasPublicStaticIsValidMethod} annotations.
*/
@Documented
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface HasPublicStaticIsValidMethods {

/**
* Returns a list of {@link HasPublicStaticIsValidMethod} instances.
*
* @return Array of annotations.
*/
HasPublicStaticIsValidMethod[] value();

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.fuin.objects4j.common;

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

import java.lang.annotation.*;

/**
Expand All @@ -10,8 +13,9 @@
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ValueOfCapables.class)
public @interface ValueOfCapable {
@Repeatable(HasPublicStaticValueOfMethods.class)
@Constraint(validatedBy = { HasPublicStaticValueOfMethodValidator.class })
public @interface HasPublicStaticValueOfMethod {

/**
* Returns the name of a public static method in the annotated class.<br>
Expand All @@ -34,4 +38,10 @@
*/
Class<?> param() default String.class;

String message() default "Does not define a public static method with the given argument type and returns boolean";

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.fuin.objects4j.common;

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
* Determines if the annotated class has:
* - a public static method with the given name
* - a parameter with the given type
* defined in the annotation and the return type of that method is the same as the annotated class.
*/
public class HasPublicStaticValueOfMethodValidator implements ConstraintValidator<HasPublicStaticValueOfMethod, Object> {

private String methodName;

private Class<?> paramClass;

@Override
public void initialize(HasPublicStaticValueOfMethod annotation) {
this.methodName = annotation.method();
this.paramClass = annotation.param();
}

@Override
public boolean isValid(Object obj, ConstraintValidatorContext context) {
try {
final Method method = obj.getClass().getMethod(methodName, paramClass);
final int modifiers = method.getModifiers();
if (!Modifier.isStatic(modifiers)) {
error(context, "Method '" + methodName + "' is not static (#1)");
return false;
}
if (method.getReturnType() != obj.getClass()) {
error(context, "Method '" + methodName + "' does not return '" + obj.getClass().getName() + "', but: " + method.getReturnType().getName() + " (#3)");
return false;
}
final Object value = method.invoke(obj, (Object) null);
if (value != null) {
error(context, "Method '" + methodName + "' is expected to return 'true' on 'null' argument, but was: " + value + " (#4)");
return false;
}
return true;
} catch (final NoSuchMethodException ex) {
error(context, "The method '" + methodName + "' is undefined or it is not public (#2)");
return false;
} catch (final IllegalAccessException | InvocationTargetException 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
Expand Up @@ -3,19 +3,19 @@
import java.lang.annotation.*;

/**
* Collection of {@link ValueOfCapable} annotations.
* Collection of {@link HasPublicStaticValueOfMethod} annotations.
*/
@Documented
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface ValueOfCapables {
public @interface HasPublicStaticValueOfMethods {

/**
* Returns a list of annotations.
*
* @return Array of annotations.
*/
ValueOfCapable[] value();
HasPublicStaticValueOfMethod[] value();

}
21 changes: 0 additions & 21 deletions src/main/java/org/fuin/objects4j/common/IsValidCapables.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
package org.fuin.objects4j.vo;

import org.fuin.objects4j.common.HasPublicStaticIsValidMethod;

import java.io.Serializable;
import java.util.UUID;
import java.util.regex.Pattern;
Expand All @@ -25,6 +27,7 @@
* Base class for UUID value objects that that overrides {@link Object#hashCode()} and {@link Object#equals(Object)} and it implements
* comparable based on the {@link #asBaseType()} method.
*/
@HasPublicStaticIsValidMethod
public abstract class AbstractUuidValueObject implements ValueObjectWithBaseType<UUID>, Comparable<AbstractUuidValueObject>, Serializable {

private static final long serialVersionUID = 1000L;
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/org/fuin/objects4j/vo/DayOfTheWeek.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.Immutable;
import org.fuin.objects4j.common.Nullable;
import org.fuin.objects4j.common.*;
import org.fuin.objects4j.common.HasPublicStaticValueOfMethod;
import org.fuin.objects4j.ui.Label;
import org.fuin.objects4j.ui.Prompt;
import org.fuin.objects4j.ui.ShortLabel;
Expand All @@ -44,6 +42,9 @@
@Tooltip("The days of the week 'Mon'-'Sun'(from Monday to Sunday) plus 'PH' (Public Holiday)")
@Prompt("Fri")
@XmlJavaTypeAdapter(DayOfTheWeekConverter.class)
@HasPublicStaticIsValidMethod
@HasPublicStaticValueOfMethod(method = "valueOf", param = String.class)
@HasPublicStaticValueOfMethod(method = "valueOf", param = DayOfWeek.class)
public final class DayOfTheWeek implements ValueObjectWithBaseType<String>, Comparable<DayOfTheWeek>, Serializable, AsStringCapable {

private static final long serialVersionUID = 1000L;
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/fuin/objects4j/vo/DayOpeningHours.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.Immutable;
import org.fuin.objects4j.common.Nullable;
import org.fuin.objects4j.common.*;
import org.fuin.objects4j.common.HasPublicStaticValueOfMethod;
import org.fuin.objects4j.ui.Prompt;
import org.fuin.objects4j.vo.HourRanges.ChangeType;

Expand All @@ -44,6 +42,8 @@
@Immutable
@Prompt("Mon 09:00-12:00+13:00-17:00")
@XmlJavaTypeAdapter(DayOpeningHoursConverter.class)
@HasPublicStaticIsValidMethod
@HasPublicStaticValueOfMethod
public final class DayOpeningHours implements ValueObjectWithBaseType<String>, Comparable<DayOpeningHours>, Serializable, AsStringCapable {

private static final long serialVersionUID = 1000L;
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/fuin/objects4j/vo/Hour.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.Immutable;
import org.fuin.objects4j.common.Nullable;
import org.fuin.objects4j.common.*;
import org.fuin.objects4j.common.HasPublicStaticValueOfMethod;
import org.fuin.objects4j.ui.Label;
import org.fuin.objects4j.ui.Prompt;
import org.fuin.objects4j.ui.ShortLabel;
Expand Down Expand Up @@ -51,6 +49,8 @@
@Tooltip("Hour of a day")
@Prompt("23:59")
@XmlJavaTypeAdapter(HourConverter.class)
@HasPublicStaticIsValidMethod
@HasPublicStaticValueOfMethod
public final class Hour extends AbstractStringValueObject {

private static final long serialVersionUID = 1000L;
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/fuin/objects4j/vo/HourRange.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.Immutable;
import org.fuin.objects4j.common.Nullable;
import org.fuin.objects4j.common.*;
import org.fuin.objects4j.common.HasPublicStaticValueOfMethod;
import org.fuin.objects4j.ui.Label;
import org.fuin.objects4j.ui.Prompt;
import org.fuin.objects4j.ui.ShortLabel;
Expand Down Expand Up @@ -51,6 +49,8 @@
@Tooltip("From hourRange of day until hourRange of day")
@Prompt("00:00-24:00")
@XmlJavaTypeAdapter(HourRangeConverter.class)
@HasPublicStaticIsValidMethod
@HasPublicStaticValueOfMethod
public final class HourRange extends AbstractStringValueObject {

private static final long serialVersionUID = 1000L;
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/org/fuin/objects4j/vo/HourRanges.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.Immutable;
import org.fuin.objects4j.common.Nullable;
import org.fuin.objects4j.common.*;
import org.fuin.objects4j.common.HasPublicStaticValueOfMethod;
import org.fuin.objects4j.ui.Prompt;

import java.util.*;
Expand All @@ -35,6 +33,9 @@
@Immutable
@Prompt("09:00-12:00+13:00-17:00")
@XmlJavaTypeAdapter(HourRangesConverter.class)
@HasPublicStaticIsValidMethod
@HasPublicStaticValueOfMethod(method = "valueOf", param = String.class)
@HasPublicStaticValueOfMethod(method = "valueOf", param = BitSet.class)
public final class HourRanges extends AbstractStringValueObject implements Iterable<HourRange> {

private static final long serialVersionUID = 1000L;
Expand Down
Loading

0 comments on commit 3ec4fa3

Please sign in to comment.