Wrapper.java

package it.fulminazzo.yagl.wrappers;

import it.fulminazzo.fulmicollection.objects.FieldEquable;
import it.fulminazzo.fulmicollection.objects.Printable;
import it.fulminazzo.fulmicollection.utils.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Field;

/**
 * A general class that represents a wrapper for another object.
 */
public abstract class Wrapper extends FieldEquable {

    /**
     * Uses {@link #check(Object, Number)} to check the given value.
     *
     * @param <N>   the type parameter
     * @param value the value
     * @return the value
     */
    protected <N extends Number> N check(final @NotNull N value) {
        return check(this, value);
    }

    /**
     * Allows checking the given number.
     * It uses the {@link Range} annotation.
     * If the given value is lower than {@link Range#min()} or higher than {@link Range#max()},
     * an {@link IllegalArgumentException} is thrown.
     *
     * @param <N>    the type parameter
     * @param object the object to check
     * @param value  the value
     * @return the passed value
     */
    public static <N extends Number> N check(final Object object, final @NotNull N value) {
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        // The first element should be the getStackTrace invocation, the second and third the 'check' methods.
        // So we are looking for the third.
        String method = trace[2].getMethodName().toLowerCase();
        if (method.equals("check")) method = trace[3].getMethodName().toLowerCase();
        if (method.startsWith("set") || method.startsWith("get")) method = method.substring(3);
        checkField(value, ReflectionUtils.getField(object, method));
        return value;
    }

    private static <N extends Number> void checkField(@NotNull N value, Field field) {
        if (field.isAnnotationPresent(Range.class)) {
            Range range = field.getAnnotation(Range.class);
            int min = range.min();
            int max = range.max();
            if (min > max) throw new InvalidRangeException(field, min, max);
            else if (value.doubleValue() < (double) min || value.doubleValue() > (double) max)
                throw new IllegalArgumentException(getExceptionMessage(field, max, min));
        }
    }

    private static @NotNull String getExceptionMessage(final @NotNull Field field, final int max, final int min) {
        final String fieldName = field.getName();

        final String message;
        if (max != Integer.MAX_VALUE && min != Integer.MIN_VALUE) message = String.format("'%s' must be between %s and %s", fieldName, min, max);
        else if (min != Integer.MIN_VALUE) message = String.format("'%s' cannot be lower than %s", fieldName, min);
        else if (max != Integer.MAX_VALUE) message = String.format("'%s' cannot be higher than %s", fieldName, max);
        else message = String.format("Invalid value provided for '%s'", fieldName);

        return message;
    }

    /**
     * Gets name.
     *
     * @return the name
     */
    public abstract String getName();

    /**
     * Compare this {@link Wrapper} with the given one
     *
     * @param wrapper the wrapper
     * @return true, if they have the same name
     */
    public boolean isSimilar(final @Nullable Wrapper wrapper) {
        return wrapper != null && getClass().equals(wrapper.getClass()) && getName().equalsIgnoreCase(wrapper.getName());
    }

    @Override
    public @NotNull String toString() {
        return Printable.convertToJson(this);
    }
}