package cn.com.ty.lift.common.verify;

import lombok.extern.slf4j.Slf4j;

import javax.validation.constraints.*;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.IDN;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.*;
import java.time.chrono.HijrahDate;
import java.time.chrono.JapaneseDate;
import java.time.chrono.MinguoDate;
import java.time.chrono.ThaiBuddhistDate;
import java.time.temporal.TemporalAccessor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;

import static java.util.regex.Pattern.CASE_INSENSITIVE;

/**
 * the validation for parameter implements {@linkplain javax.validation.constraints},
 * reform from hibernate validator (v6.0.16.Final)
 *
 * @author wcz
 * @since 2020/2/15
 */
@Slf4j
public class Validator {
    /**
     * the name of the verify method.
     */
    private static final String                      validateMethodName               = "validate";
    /**
     * the list of validation annotation & method can be work in {@code javax.validation.constraints.*}
     */
    private static final Map<Class<?>, Method>       annotationValidMethodCache       = new ConcurrentHashMap<>();
    /**
     * the cache for annotations of field.
     */
    private static final Map<Field, Annotation[]>    annotationOnFieldCache           = new ConcurrentHashMap<>();
    /**
     * MethodHandle
     */
    private static final Map<Class<?>, MethodHandle> annotationValidMethodHandleCache = new ConcurrentHashMap<>();
    /**
     * the max length for a valid email address local part.
     */
    private static final int                         MAX_LOCAL_PART_LENGTH            = 64;
    /**
     * the regular expression for local part of a valid email address.
     */
    private static final String                      LOCAL_PART_ATOM                  = "[a-z0-9!#$%&'*+/=?^_`{|}~\u0080-\uFFFF-]";
    /**
     * the regular expression for the local part of an email address.
     */
    private static final String                      LOCAL_PART_INSIDE_QUOTES_ATOM = "([a-z0-9!#$%&'*.(),<>\\[\\]:;  @+/=?^_`{|}~\u0080-\uFFFF-]|\\\\\\\\|\\\\\\\")";
    /**
     * Regular expression for the local part of an email address (everything before '@')
     */
    private static final java.util.regex.Pattern     LOCAL_PART_PATTERN            = java.util.regex.Pattern.compile(
            "(" + LOCAL_PART_ATOM + "+|\"" + LOCAL_PART_INSIDE_QUOTES_ATOM + "+\")" +
                    "(\\." + "(" + LOCAL_PART_ATOM + "+|\"" + LOCAL_PART_INSIDE_QUOTES_ATOM + "+\")" + ")*", CASE_INSENSITIVE
    );
    /**
     * This is the maximum length of a domain name. But be aware that each label (parts separated by a dot) of the
     * domain name must be at most 63 characters long. This is verified by {@link IDN#toASCII(String)}.
     */
    private static final int                         MAX_DOMAIN_PART_LENGTH        = 255;
    private static final String                      DOMAIN_CHARS_WITHOUT_DASH     = "[a-z\u0080-\uFFFF0-9!#$%&'*+/=?^_`{|}~]";
    private static final String                      DOMAIN_LABEL                  = "(" + DOMAIN_CHARS_WITHOUT_DASH + "-*)*" + DOMAIN_CHARS_WITHOUT_DASH + "+";
    /**
     * the regex for check domain
     */
    private static final String                      DOMAIN                        = DOMAIN_LABEL + "+(\\." + DOMAIN_LABEL + "+)*";
    /**
     * regex for check IP v4
     */
    private static final String                      IP_DOMAIN                     = "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}";
    /**
     * IP v6 regex taken from http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
     */
    private static final String                      IP_V6_DOMAIN                  = "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))";

    /**
     * Regular expression for the domain part of an URL
     * A host string must be a domain string, an IPv4 address string, or "[", followed by an IPv6 address string, followed by "]".
     */
    private static final java.util.regex.Pattern DOMAIN_PATTERN       = java.util.regex.Pattern.compile(DOMAIN + "|\\[" + IP_V6_DOMAIN + "\\]", CASE_INSENSITIVE);
    /**
     * Regular expression for the domain part of an email address (everything after '@')
     */
    private static final java.util.regex.Pattern EMAIL_DOMAIN_PATTERN = java.util.regex.Pattern.compile(
            DOMAIN + "|\\[" + IP_DOMAIN + "\\]|" + "\\[IPv6:" + IP_V6_DOMAIN + "\\]", CASE_INSENSITIVE
    );
    /**
     * the minimum value of compare two number for the infinity check when double or float.
     */
    private static final OptionalInt             LESS_THAN            = OptionalInt.of(-1);
    /**
     * the empty OptionalInt(value = 0) of compare two number for the infinity check when double or float.
     */
    private static final OptionalInt             FINITE_VALUE         = OptionalInt.empty();
    /**
     * the maximun value of compare two number for the infinity check when double or float.
     */
    private static final OptionalInt             GREATER_THAN         = OptionalInt.of(1);
    /**
     * short 0
     */
    private static final short                   SHORT_ZERO           = (short) 0;
    /**
     * byte 0
     */
    private static final byte                    BYTE_ZERO            = (byte) 0;
    /**
     * to private the constructor.
     */
    private Validator() {
    }
    /**
     * collect all valid validation annotation from the methods.
     */
    static {
        collectValidAnnotationMethod();
    }

    private static void collectValidAnnotationMethod() {
        Method[] declaredMethods = ValidateAction.class.getDeclaredMethods();
        List<Method> validateMethods = Arrays.stream(declaredMethods).filter(method -> validateMethodName.equals(method.getName())).collect(Collectors.toList());
        isTrue(validateMethods.isEmpty(), "No any method named '%s' in ValidateAction.", validateMethodName);
        for (Method validateMethod : validateMethods) {
            Optional<Class<?>> classOptional = Arrays.stream(validateMethod.getParameterTypes()).filter(Annotation.class::isAssignableFrom).findFirst();
            classOptional.ifPresent(validateAnnotation -> {
                if (!validateMethod.isAccessible()) {
                    validateMethod.setAccessible(true);
                }
                annotationValidMethodCache.put(validateAnnotation, validateMethod);
                log.info("@interface: {}", validateAnnotation.getCanonicalName());
            });
        }
        isTrue(annotationValidMethodCache.isEmpty(), "No valid validation annotation was resolved in ValidateAction.");
    }
    /**
     * to do verify by using the singleton instance.
     *
     * @param logWrite {@code true} log information, otherwise no log to file.
     * @param object   the target object to verify.
     * @param fields   the list of fields to verify.
     */
    public static void valid(final boolean logWrite, final Object object, final String... fields) {
        new ValidateAction(logWrite, object, fields).action();
    }

    /**
     * to do verify by using the singleton instance.
     *
     * @param object the target object to verify.
     * @param fields the list of fields to verify.
     */
    public static void valid(final Object object, final String... fields) {
        new ValidateAction(object, fields).action();
    }
    /**
     * get the systemDefaultZone {@link Clock#systemDefaultZone()}. for the date compare.
     */
    private static Clock systemDefaultClock() {
        return Clock.offset(Clock.systemDefaultZone(), Duration.ZERO.abs().negated());
    }

    private static <T> boolean isEmpty(T[] array) {
        return array == null || array.length == 0;
    }

    private static <T> boolean notEmpty(T[] array) {
        return array != null && array.length > 0;
    }

    private static boolean notBlank(String value) {
        return null != value && !value.trim().isEmpty();
    }

    /**
     * verify the value is instance of the Number.
     *
     * @param value any value.
     * @return if return {@code true}, the value is Number, otherwise no
     */
    private static boolean isNumber(Object value) {
        return (value instanceof Number);
    }

    /**
     * verify the value is instance of the CharSequence.
     *
     * @param value any value.
     * @return if return {@code true}, the value is CharSequence, otherwise no
     */
    private static boolean isCharSequence(Object value) {
        return (value instanceof CharSequence);
    }

    /**
     * verify the value is instance of the BigDecimal.
     *
     * @param value any value.
     * @return if return {@code true}, the value is BigDecimal, otherwise no
     */
    private static boolean isBigDecimal(Object value) {
        return (value instanceof BigDecimal);
    }

    /**
     * verify the value is instance of the BigInteger.
     *
     * @param value any value.
     * @return if return {@code true}, the value is BigInteger, otherwise no
     */
    private static boolean isBigInteger(Object value) {
        return (value instanceof BigInteger);
    }

    /**
     * verify the value is instance of the Long.
     *
     * @param value any value.
     * @return if return {@code true}, the value is Long, otherwise no
     */
    private static boolean isLong(Object value) {
        return (value instanceof Long);
    }

    /**
     * verify the value is instance of the Double.
     *
     * @param value any value.
     * @return if return {@code true}, the value is Double, otherwise no
     */
    private static boolean isDouble(Object value) {
        return (value instanceof Double);
    }

    /**
     * verify the value is instance of the Float.
     *
     * @param value any value.
     * @return if return {@code true}, the value is Float, otherwise no
     */
    private static boolean isFloat(Object value) {
        return (value instanceof Float);
    }

    /**
     * verify the value is instance of the TemporalAccessor(java 8).
     *
     * @param value any value.
     * @return if return {@code true}, the value is TemporalAccessor, otherwise no
     */
    private static boolean isTemporalAccessor(Object value) {
        return (value instanceof TemporalAccessor);
    }

    /**
     * verify the value is instance of the java.util.Date.
     *
     * @param value any value.
     * @return if return {@code true}, the value is Date, otherwise no
     */
    private static boolean isDate(Object value) {
        return (value instanceof Date);
    }

    /**
     * verify the value is instance of the java.util.Calendar.
     *
     * @param value any value.
     * @return if return true, the value is Calendar, otherwise no
     */
    private static boolean isCalendar(Object value) {
        return (value instanceof Calendar);
    }

    /**
     * verify whether a double value is infinity with less or greater.
     *
     * @param number     any double value.
     * @param treatNanAs when the value isn't a number, return this value.
     * @return a OptionalInt value with the result of compare to infinity.
     * -1 for equal {@link Double#NEGATIVE_INFINITY}
     * 1 for equal {@link Double#POSITIVE_INFINITY}
     */
    private static OptionalInt infinityCheck(Double number, OptionalInt treatNanAs) {
        if (number == Double.NEGATIVE_INFINITY) {
            return LESS_THAN;
        } else if (number.isNaN()) {
            return treatNanAs;
        } else if (number == Double.POSITIVE_INFINITY) {
            return GREATER_THAN;
        } else {
            return FINITE_VALUE;
        }
    }

    /**
     * verify whether a float vlaue is infinity with less or greater.
     *
     * @param number     any float value.
     * @param treatNanAs when the value isn't a number, return this value.
     * @return a OptionalInt value with the result of compare to infinity.
     * -1 for equal {@link Float#NEGATIVE_INFINITY}
     * 1 for equal {@link Float#POSITIVE_INFINITY}
     */
    private static OptionalInt infinityCheck(Float number, OptionalInt treatNanAs) {
        if (number == Float.NEGATIVE_INFINITY) {
            return LESS_THAN;
        } else if (number.isNaN()) {
            return treatNanAs;
        } else if (number == Float.POSITIVE_INFINITY) {
            return GREATER_THAN;
        } else {
            return FINITE_VALUE;
        }
    }

    /**
     * compare two BigDecimal value
     *
     * @param value      any object value
     * @param val        the BigDecimal value transform from the object value
     * @param bounds     the boundary of the maximum or minimum
     * @param treatNanAs return for the value isn't number when check for infinity
     * @return return {@code -1} for the val is less than boundary, {@code 0} for equal,{@code 1} for greater than boundary.
     */
    private static int decimalComparator(Object value, BigDecimal val, BigDecimal bounds, OptionalInt treatNanAs) {
        if (isLong(value) || isBigInteger(value) || isBigDecimal(value)) {
            return val.compareTo(bounds);
        }
        if (isDouble(value)) {
            OptionalInt infinity = infinityCheck((Double) value, treatNanAs);
            if (infinity.isPresent()) {
                return infinity.getAsInt();
            } else {
                return val.compareTo(bounds);
            }
        }
        if (isFloat(value)) {
            OptionalInt infinity = infinityCheck((Float) value, treatNanAs);
            if (infinity.isPresent()) {
                return infinity.getAsInt();
            } else {
                return val.compareTo(bounds);
            }
        }
        return val.compareTo(bounds);
    }

    /**
     * get the length or size of the value.
     *
     * @param value any object value
     * @return the length or size of the value when it's working, but return zero when the value is null.
     */
    private static int length(Object value) {
        if (Objects.isNull(value)) {
            return 0;
        }
        if (isCharSequence(value)) {
            return ((CharSequence) value).length();
        }
        if (value instanceof Collection) {
            return ((Collection<?>) value).size();
        }
        if (value instanceof Map) {
            return ((Map<?, ?>) value).size();
        }
        if (value.getClass().isArray()) {
            return Array.getLength(value);
        }
        int count;
        if (value instanceof Iterator) {
            Iterator<?> iterator = (Iterator<?>) value;
            count = 0;
            while (iterator.hasNext()) {
                count++;
                iterator.next();
            }
            return count;
        }
        if (value instanceof Enumeration) {
            Enumeration<?> enumeration = (Enumeration<?>) value;
            count = 0;
            while (enumeration.hasMoreElements()) {
                count++;
                enumeration.nextElement();
            }
            return count;
        }
        return -1;
    }

    /**
     * compare two number value with less than or greater than.
     *
     * @param value      any number value
     * @param bounds     the other value
     * @param treatNanAs return when the value is {@code NaN}.
     * @return {@code -1} value less than limit, {@code 0} value equal limit, {@code 1} value greater than limit.
     */
    private static int numberComparator(Number value, long bounds, OptionalInt treatNanAs) {
        if (isLong(value)) {
            return ((Long) value).compareTo(bounds);
        }
        if (isDouble(value)) {
            Double val = (Double) value;
            OptionalInt infinity = infinityCheck(val, treatNanAs);
            if (infinity.isPresent()) {
                return infinity.getAsInt();
            } else {
                return Double.compare(val, bounds);
            }
        }
        if (isFloat(value)) {
            Float val = (Float) value;
            OptionalInt infinity = infinityCheck(val, treatNanAs);
            if (infinity.isPresent()) {
                return infinity.getAsInt();
            } else {
                return Float.compare(val, bounds);
            }
        }
        if (isBigDecimal(value)) {
            return ((BigDecimal) value).compareTo(BigDecimal.valueOf(bounds));
        }
        if (isBigInteger(value)) {
            return ((BigInteger) value).compareTo(BigInteger.valueOf(bounds));
        }
        return Long.compare(value.longValue(), bounds);
    }

    /**
     * get the sign number of the value.
     *
     * @param value      any number value
     * @param treatNanAs return when the value equal infinity.
     * @return {@code -1} the value less than zero, {@code 0} the value equal zero, {@code 1} the value greater than zero.
     */
    private static int signum(Number value, OptionalInt treatNanAs) {
        if (isLong(value)) {
            return Long.signum((Long) value);
        }
        if (value instanceof Integer) {
            return Integer.signum((Integer) value);
        }
        if (isBigDecimal(value)) {
            return ((BigDecimal) value).signum();
        }
        if (isBigInteger(value)) {
            return ((BigInteger) value).signum();
        }
        if (isDouble(value)) {
            Double val = (Double) value;
            OptionalInt infinity = infinityCheck(val, treatNanAs);
            if (infinity.isPresent()) {
                return infinity.getAsInt();
            } else {
                return val.compareTo(0D);
            }
        }
        if (isFloat(value)) {
            Float val = (Float) value;
            OptionalInt infinity = infinityCheck(val, treatNanAs);
            if (infinity.isPresent()) {
                return infinity.getAsInt();
            } else {
                return val.compareTo(0F);
            }
        }
        if (value instanceof Byte) {
            return ((Byte) value).compareTo(BYTE_ZERO);
        }
        if (value instanceof Short) {
            return ((Short) value).compareTo(SHORT_ZERO);
        }
        return Double.compare(value.doubleValue(), 0D);
    }

    /**
     * the implements verify method for validation annotation in {@linkplain javax.validation.constraints}
     */
    private static class ValidateAction {
        private String   message;
        private int      code;
        private boolean  logWrite = true;
        private Object   object;
        private String[] fields;

        private ValidateAction(boolean logWrite, Object object, String... fields) {
            this.logWrite = logWrite;
            this.object = object;
            this.fields = fields;
        }

        private ValidateAction(Object object, String... fields) {
            this.object = object;
            this.fields = fields;
        }

        /**
         * new a BigDecimal object with a CharSequence value.
         *
         * @param value any CharSequence value
         * @return a new {@link BigDecimal} object or null when occur any exception.
         */
        private BigDecimal newBigDecimal(CharSequence value) {
            try {
                BigDecimal bd;
                if (value instanceof String) {
                    bd = new BigDecimal((String) value);
                } else {
                    bd = new BigDecimal(value.toString());
                }
                return bd;
            } catch (Exception e) {
                setIllegalArgMessage("Failed to convert '%s' to a valid BigDecimal. Exception: %s", value, e.getMessage());
                return null;
            }
        }

        /**
         * new a BigDecimal object with a Number value, base on the Specific type of the value.
         *
         * @param value any Number value
         * @return a new {@link BigDecimal} object or null when occur any exception.
         */
        private BigDecimal newBigDecimal(Number value) {
            try {
                BigDecimal bd;
                if (isLong(value)) {
                    bd = BigDecimal.valueOf((Long) value);
                } else if (isBigDecimal(value)) {
                    bd = ((BigDecimal) value);
                } else if (isBigInteger(value)) {
                    bd = new BigDecimal((BigInteger) value);
                } else if (isDouble(value)) {
                    bd = BigDecimal.valueOf((Double) value);
                } else if (isFloat(value)) {
                    bd = BigDecimal.valueOf((Float) value);
                } else {
                    bd = BigDecimal.valueOf(value.doubleValue());
                }
                return bd;
            } catch (Exception e) {
                setIllegalArgMessage("Failed to convert '%s' to a valid BigDecimal. Exception: %s", value, e.getMessage());
                return null;
            }
        }

        /**
         * set the verify message in the ThreadLocal.(code : 1)
         */
        private void setValidateMessage(String message, Object... values) {
            this.code = 1;
            this.message = format(message, values);
        }

        /**
         * set the illegal argument message in the ThreadLocal.(code : 2)
         */
        private void setIllegalArgMessage(String message, Object... values) {
            this.code = 2;
            this.message = format(message, values);
        }

        /**
         * verify whether the value isn't {@code null}.
         *
         * @param value      any object value.
         * @param annotation the {@link NotNull} annotation get from the field.
         * @return if return {@code true} that the value isn't null, otherwise no.
         */
        private boolean validate(Object value, NotNull annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            return null != value;
        }

        /**
         * verify whether the value is {@code null}.
         *
         * @param value      any object value.
         * @param annotation the {@link Null} annotation get from the field.
         * @return if return {@code true} that the value is null, otherwise no.
         */
        private boolean validate(Object value, Null annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            return Objects.isNull(value);
        }

        /**
         * verify whether the value is {@code true}.
         *
         * @param value      any object value, but only work for {@link Boolean}
         * @param annotation the {@link AssertTrue} annotation get from the field.
         * @return if return {@code true} that the value is true, otherwise no.
         */
        private boolean validate(Object value, AssertTrue annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (value instanceof Boolean) {
                return ((Boolean) value);
            } else {
                setIllegalArgMessage("The @AssertTrue only supports Boolean.");
                return false;
            }
        }

        /**
         * verify whether the value is {@code false}.
         *
         * @param value      any object value, but only work for {@link Boolean}
         * @param annotation the {@link AssertFalse} annotation get from the field.
         * @return if return {@code true} that the value is false, otherwise no.
         */
        private boolean validate(Object value, AssertFalse annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (value instanceof Boolean) {
                return !((Boolean) value);
            } else {
                setIllegalArgMessage("The @AssertFalse only supports Boolean.");
                return false;
            }
        }

        /**
         * verify whether the value is less than the bigdecimal maximum value.
         *
         * @param value      any object value, but only work for {@link Number} and {@link CharSequence}
         * @param annotation the {@link DecimalMax} annotation get from the field.
         * @return if return {@code true} that the value is less than the bigdecimal maximum, otherwise no.
         */
        private boolean validate(Object value, DecimalMax annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isNumber = isNumber(value);
            final boolean isCharSequence = isCharSequence(value);
            if (!isNumber && !isCharSequence) {
                setIllegalArgMessage("The @DecimalMax only supports Number & CharSequence.");
                return false;
            }
            final String maxValue = annotation.value();
            if (Objects.isNull(maxValue)) {
                setIllegalArgMessage("The value of @DecimalMax is null, a invalid BigDecimal format.");
                return false;
            }
            final BigDecimal max = newBigDecimal(maxValue);
            if (Objects.isNull(max)) {
                return false;
            }
            final BigDecimal val;
            if (isNumber) {
                val = newBigDecimal((Number) value);
            } else {
                val = newBigDecimal((CharSequence) value);
            }
            if (Objects.isNull(val)) {
                return false;
            }
            final int compare = decimalComparator(value, val, max, GREATER_THAN);
            final boolean inclusive = annotation.inclusive();
            //inclusive ? comparisonResult <= 0 : comparisonResult < 0;
            if (inclusive) {
                return compare <= 0;
            } else {
                return compare < 0;
            }
        }

        /**
         * verify whether the value is greater than the bigdecimal minimum value.
         *
         * @param value      any object value, but only work for {@link Number} and {@link CharSequence}
         * @param annotation the {@link DecimalMin} annotation get from the field.
         * @return if return {@code true} that the value is greater than the bigdecimal minimum, otherwise no.
         */
        private boolean validate(Object value, DecimalMin annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isNumber = isNumber(value);
            final boolean isCharSequence = isCharSequence(value);
            if (!isNumber && !isCharSequence) {
                setIllegalArgMessage("The @DecimalMin only supports Number & CharSequence.");
                return false;
            }
            final String minValue = annotation.value();
            if (Objects.isNull(minValue)) {
                setIllegalArgMessage("The value of @DecimalMin is null, a invalid BigDecimal format.");
                return false;
            }
            final BigDecimal min = newBigDecimal(minValue);
            if (Objects.isNull(min)) {
                return false;
            }
            final BigDecimal val;
            if (isNumber) {
                val = newBigDecimal((Number) value);
            } else {
                val = newBigDecimal((CharSequence) value);
            }
            if (Objects.isNull(val)) {
                return false;
            }
            final int compare = decimalComparator(value, val, min, LESS_THAN);
            final boolean inclusive = annotation.inclusive();
            //inclusive ? comparisonResult >= 0 : comparisonResult > 0;
            if (inclusive) {
                return compare >= 0;
            } else {
                return compare > 0;
            }
        }

        /**
         * verify whether the value isn't null and not empty.
         *
         * @param value      any object value,but only work for {@link CharSequence},{@link Collection},{@link Map},{@link Array},{@link Iterator}and {@link Enumeration}
         * @param annotation the {@link NotEmpty} annotation get from the field.
         * @return if return {@code true} that the value isn't empty(with some length or size), otherwise no.
         */
        private boolean validate(Object value, NotEmpty annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final int length = length(value);
            if (-1 == length) {
                setIllegalArgMessage("The @NotEmpty only supports CharSequence & Collection & Map & Array & Iterator & Enumeration.");
                return false;
            }
            // length > 0
            return length > 0;
        }

        /**
         * verify whether the value satisfy the size with maximum and minimun.
         *
         * @param value      any object value, but only work for {@link CharSequence},{@link Collection},{@link Map},{@link Array},{@link Iterator}and {@link Enumeration}
         * @param annotation the {@link Size} annotation get from the field.
         * @return if return {@code true} that the value satisfy the size, otherwise no.
         */
        private boolean validate(Object value, Size annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            final int min = annotation.min();
            final int max = annotation.max();
            String message = annotation.message();
            if (notBlank(message)) {
                if (message.contains("{min}")) {
                    message = message.replace("{min}", String.valueOf(min));
                }
                if (message.contains("{max}")) {
                    message = message.replace("{max}", String.valueOf(max));
                }
            }
            setValidateMessage(message);
            if (Objects.isNull(value)) {
                return false;
            }
            if (min < 0) {
                setIllegalArgMessage("The min (%s) parameter of @Size cannot be negative.", min);
                return false;
            }
            if (max < 0) {
                setIllegalArgMessage("The max (%s) parameter of @Size cannot be negative.", max);
                return false;
            }
            if (min > max) {
                setIllegalArgMessage("The min (%s) must be less or equals max (%s) of @Size.", min, max);
                return false;
            }
            final int length = length(value);
            if (-1 == length) {
                setIllegalArgMessage("The @Size only supports CharSequence & Collection & Map & Array & Iterator & Enumeration.");
                return false;
            }
            //size >= min && size <= max
            return length >= min && length <= max;
        }

        /**
         * verify that the {@code Number} being validated matches the pattern
         *
         * @param value      any object value, but only work for {@link CharSequence} and {@link Number}
         * @param annotation the {@link Digits} annotation get from the field.
         * @return if return {@code true} that the value is matches pattern, otherwise no.
         */
        private boolean validate(Object value, Digits annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            final int maxInteger = annotation.integer();
            final int maxFraction = annotation.fraction();
            String message = annotation.message();
            if (notBlank(message)) {
                if (message.contains("{integer}")) {
                    message = message.replace("{integer}", String.valueOf(maxInteger));
                }
                if (message.contains("{fraction}")) {
                    message = message.replace("{fraction}", String.valueOf(maxFraction));
                }
            }
            setValidateMessage(message);
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isNumber = isNumber(value);
            final boolean isCharSequence = isCharSequence(value);
            if (!isNumber && !isCharSequence) {
                setIllegalArgMessage("The @Digits only supports Number & CharSequence.");
                return false;
            }
            if (maxInteger < 0) {
                setIllegalArgMessage("The length of the integer '%s' part of @Digits cannot be negative.", maxInteger);
                return false;
            }
            if (maxFraction < 0) {
                setIllegalArgMessage("The length of the fraction '%s' part of @Digits cannot be negative.", maxFraction);
                return false;
            }
            BigDecimal val;
            if (isNumber) {
                if (isBigDecimal(value)) {
                    val = (BigDecimal) value;
                } else {
                    val = newBigDecimal(value.toString());
                    if (Objects.isNull(val)) {
                        return false;
                    }
                    val = val.stripTrailingZeros();
                }
            } else {
                val = newBigDecimal((CharSequence) value);
            }
            if (Objects.isNull(val)) {
                return false;
            }
            int integerPart = val.precision() - val.scale();
            int fractionPart = val.scale() < 0 ? 0 : val.scale();
            //maxInteger >= integerPart && maxFraction >= fractionPart
            return maxInteger >= integerPart && maxFraction >= fractionPart;
        }

        /**
         * verify that a character sequence is not {@code null} nor empty after removing any leading or trailing whitespace.
         *
         * @param value      any object value, but only work for {@link CharSequence}
         * @param annotation the {@link NotBlank} annotation get from the field.
         * @return if return {@code true} that the value isn't blank, otherwise no.
         */
        private boolean validate(Object value, NotBlank annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isCharSequence(value)) {
                setIllegalArgMessage("The @NotBlank only supports CharSequence.");
                return false;
            }
            return ((CharSequence) value).toString().trim().length() > 0;
        }

        /**
         * verify whether the value is a valid email address.
         *
         * @param value      any object value, but only work for {@link CharSequence}
         * @param annotation the {@link Email} annotation get from the field.
         * @return if return {@code true} that the value is a valid email address, otherwise no.
         */
        private boolean validate(Object value, Email annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isCharSequence(value)) {
                setIllegalArgMessage("The @Email only supports CharSequence.");
                return false;
            }
            //@Email need trim
            final String val = ((CharSequence) value).toString().trim();
            if (val.isEmpty()) {
                return false;
            }

            // cannot split email string at @ as it can be a part of quoted local part of email.
            // so we need to split at a position of last @ present in the string:
            int splitPosition = val.lastIndexOf('@');
            if (splitPosition < 0) {
                return false;
            }
            String localPart = val.substring(0, splitPosition);
            String domainPart = val.substring(splitPosition + 1);
            // verify the local part
            if (localPart.length() > MAX_LOCAL_PART_LENGTH) {
                return false;
            }
            if (!LOCAL_PART_PATTERN.matcher(localPart).matches()) {
                return false;
            }
            // verify the domain part
            // if we have a trailing dot the domain part we have an invalid email address.
            // the regular expression match would take care of this, but IDN.toASCII drops the trailing '.'
            if (domainPart.endsWith(".")) {
                return false;
            }
            Matcher matcher = EMAIL_DOMAIN_PATTERN.matcher(domainPart);
            if (!matcher.matches()) {
                return false;
            }
            String ascii;
            try {
                ascii = IDN.toASCII(domainPart);
            } catch (IllegalArgumentException e) {
                setIllegalArgMessage("Failed to convert domain string to ASCII code. Exception: %s", e.getMessage());
                return false;
            }
            if (MAX_DOMAIN_PART_LENGTH < ascii.length()) {
                return false;
            }

            final Pattern.Flag[] flags = annotation.flags();
            final String regexp = annotation.regexp();
            int intFlag = 0;
            for (Pattern.Flag flag : flags) {
                intFlag = intFlag | flag.getValue();
            }
            java.util.regex.Pattern pattern = null;
            // we only apply the regexp if there is one to apply
            if (!".*".equals(regexp) || flags.length > 0) {
                try {
                    pattern = java.util.regex.Pattern.compile(regexp, intFlag);
                } catch (PatternSyntaxException e) {
                    setIllegalArgMessage("Failed to Compile the regexp and flag for @Email. Exception: %s", e.getMessage());
                    return false;
                }
            }
            if (Objects.isNull(pattern)) {
                setIllegalArgMessage("The regexp for @Email is Invalid regular expression.");
                return false;
            }
            return pattern.matcher(val).matches();
        }

        /**
         * verify whether the value is matches the pattern.
         *
         * @param value      any object value, but only work for {@link CharSequence}
         * @param annotation the {@link Pattern} annotation get from the field.
         * @return if return {@code true} that the value is mathches the pattern, otherwise no.
         */
        private boolean validate(Object value, Pattern annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isCharSequence(value)) {
                setIllegalArgMessage("The @Pattern only supports CharSequence.");
                return false;
            }
            //@Pattern no trim
            final String val = ((CharSequence) value).toString();
            if (val.isEmpty()) {
                return false;
            }

            final Pattern.Flag[] flags = annotation.flags();
            final String regexp = annotation.regexp();
            int intFlag = 0;
            for (Pattern.Flag flag : flags) {
                intFlag = intFlag | flag.getValue();
            }
            java.util.regex.Pattern pattern;
            try {
                pattern = java.util.regex.Pattern.compile(regexp, intFlag);
            } catch (PatternSyntaxException e) {
                setIllegalArgMessage("Failed to Compile the regexp and flag for @Pattern. Exception: %s", e.getMessage());
                return false;
            }
            if (Objects.isNull(pattern)) {
                setIllegalArgMessage("The regexp for @Pattern is Invalid regular expression.");
                return false;
            }
            return pattern.matcher(val).matches();
        }

        /**
         * verify whether the value less then or equal the maximum value.
         *
         * @param value      any object value, but only work for {@link CharSequence} and {@link Number}
         * @param annotation the {@link Max} annotation get from the field.
         * @return if return {@code ture} that the value is less than or equal the maximum, otherwise no.
         */
        private boolean validate(Object value, Max annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isNumber = isNumber(value);
            final boolean isCharSequence = isCharSequence(value);
            if (!isCharSequence && !isNumber) {
                setIllegalArgMessage("The @Max only supports Number & CharSequence.");
                return false;
            }
            final long max = annotation.value();
            final int compare;
            if (isNumber) {
                compare = numberComparator((Number) value, max, GREATER_THAN);
            } else {
                String v = ((CharSequence) value).toString().trim();
                if (v.isEmpty()) {
                    return false;
                }
                BigDecimal val = newBigDecimal(v);
                if (Objects.isNull(val)) {
                    return false;
                }
                compare = val.compareTo(BigDecimal.valueOf(max));
            }
            //compare <= 0
            return compare <= 0;
        }

        /**
         * verify whether the value greater then or equal the minimum value.
         *
         * @param value      any object value, but only work for {@link CharSequence} and {@link Number}
         * @param annotation the {@link Max} annotation get from the field.
         * @return if return {@code true} that the value is less than or equal the minimum, otherwise no.
         */
        private boolean validate(Object value, Min annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isNumber = isNumber(value);
            final boolean isCharSequence = isCharSequence(value);
            if (!isCharSequence && !isNumber) {
                setIllegalArgMessage("The @Min only supports Number & CharSequence.");
                return false;
            }
            final long min = annotation.value();
            final int compare;
            if (isNumber) {
                compare = numberComparator((Number) value, min, LESS_THAN);
            } else {
                String v = ((CharSequence) value).toString().trim();
                if (v.isEmpty()) {
                    return false;
                }
                BigDecimal val = newBigDecimal(v);
                if (Objects.isNull(val)) {
                    return false;
                }
                compare = val.compareTo(BigDecimal.valueOf(min));
            }
            //compare >= 0
            return compare >= 0;
        }

        /**
         * verify whether the value
         *
         * @param value      any object value, but only work for {@link Number}
         * @param annotation the {@link Negative} annotation get from the field.
         * @return
         */
        private boolean validate(Object value, Negative annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isNumber(value)) {
                setIllegalArgMessage("The @Negative only supports Number.");
                return false;
            }

            return signum((Number) value, GREATER_THAN) < 0;
        }

        /**
         * @param value      any object value, but only work for {@link Number}
         * @param annotation the {@link NegativeOrZero} annotation get from the field.
         * @return
         */
        private boolean validate(Object value, NegativeOrZero annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isNumber(value)) {
                setIllegalArgMessage("The @NegativeOrZero only supports Number.");
                return false;
            }

            return signum((Number) value, GREATER_THAN) <= 0;
        }

        /**
         * @param value      any object value, but only work for {@link Number}
         * @param annotation the {@link Positive} annotation get from the field.
         * @return
         */
        private boolean validate(Object value, Positive annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isNumber(value)) {
                setIllegalArgMessage("The @Positive only supports Number.");
                return false;
            }

            return signum((Number) value, LESS_THAN) > 0;
        }

        /**
         * @param value      any object value, but only work for {@link Number}
         * @param annotation the {@link PositiveOrZero} annotation get from the field.
         * @return
         */
        private boolean validate(Object value, PositiveOrZero annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            if (!isNumber(value)) {
                setIllegalArgMessage("The @PositiveOrZero only supports Number.");
                return false;
            }

            return signum((Number) value, LESS_THAN) >= 0;
        }

        /**
         * compare the TemporalAccessor value with now , verify whether the value is before or after now.
         *
         * @param value              any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
         * @param isTemporalAccessor whether the value is instance of {@link TemporalAccessor}
         * @param isDate             whether the value is instance of {@link Date}
         * @return {@code -1} the value is before now, {@code 0} is now, {@code 1} after now. otherwise {@code -2} not support the type.
         */
        private int dateComparator(Object value, boolean isTemporalAccessor, boolean isDate) {
            Clock clock = systemDefaultClock();
            int compare;
            if (isTemporalAccessor) {
                if (value instanceof Instant) {
                    compare = ((Instant) value).compareTo(Instant.now(clock));
                } else if (value instanceof LocalDateTime) {
                    compare = ((LocalDateTime) value).compareTo(LocalDateTime.now(clock));
                } else if (value instanceof LocalDate) {
                    compare = ((LocalDate) value).compareTo(LocalDate.now(clock));
                } else if (value instanceof LocalTime) {
                    compare = ((LocalTime) value).compareTo(LocalTime.now(clock));
                } else if (value instanceof MonthDay) {
                    compare = ((MonthDay) value).compareTo(MonthDay.now(clock));
                } else if (value instanceof HijrahDate) {
                    compare = ((HijrahDate) value).compareTo(HijrahDate.now(clock));
                } else if (value instanceof JapaneseDate) {
                    compare = ((JapaneseDate) value).compareTo(JapaneseDate.now(clock));
                } else if (value instanceof MinguoDate) {
                    compare = ((MinguoDate) value).compareTo(MinguoDate.now(clock));
                } else if (value instanceof OffsetDateTime) {
                    compare = ((OffsetDateTime) value).compareTo(OffsetDateTime.now(clock));
                } else if (value instanceof OffsetTime) {
                    compare = ((OffsetTime) value).compareTo(OffsetTime.now(clock));
                } else if (value instanceof ThaiBuddhistDate) {
                    compare = ((ThaiBuddhistDate) value).compareTo(ThaiBuddhistDate.now(clock));
                } else if (value instanceof Year) {
                    compare = ((Year) value).compareTo(Year.now(clock));
                } else if (value instanceof YearMonth) {
                    compare = ((YearMonth) value).compareTo(YearMonth.now(clock));
                } else if (value instanceof ZonedDateTime) {
                    compare = ((ZonedDateTime) value).compareTo(ZonedDateTime.now(clock));
                } else {
                    setIllegalArgMessage("The '%s' is not a supported TemporalAccessor class temporarily.", value.getClass().getCanonicalName());
                    compare = Integer.MAX_VALUE;
                }
            } else if (isDate) {
                Date val = (Date) value;
                compare = val.toInstant().compareTo(Instant.now(clock));
            } else {
                Calendar val = (Calendar) value;
                compare = val.toInstant().compareTo(Instant.now(clock));
            }
            return compare;
        }

        /**
         * verify whether the value is a future time.
         *
         * @param value      any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
         * @param annotation the {@link Future} annotation get from the field.
         * @return if return {@code true} that the value is a future time, otherwise no.
         */
        private boolean validate(Object value, Future annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isTemporalAccessor = isTemporalAccessor(value);
            final boolean isDate = isDate(value);
            final boolean isCalendar = isCalendar(value);
            if (!isTemporalAccessor && !isDate && !isCalendar) {
                setIllegalArgMessage("The @Future only supports TemporalAccessor & Calendar & Date.");
                return false;
            }
            final int compare = dateComparator(value, isTemporalAccessor, isDate);
            if (Integer.MAX_VALUE == compare) {
                return false;
            }
            //compare > 0
            return compare > 0;
        }

        /**
         * verify whether the value is a future time or present.
         *
         * @param value      any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
         * @param annotation the {@link FutureOrPresent} annotation get from the field.
         * @return if return {@code true} that the value is a future time or present, otherwise no.
         */
        private boolean validate(Object value, FutureOrPresent annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isTemporalAccessor = isTemporalAccessor(value);
            final boolean isDate = isDate(value);
            final boolean isCalendar = isCalendar(value);
            if (!isTemporalAccessor && !isDate && !isCalendar) {
                setIllegalArgMessage("The @FutureOrPresent only supports TemporalAccessor & Calendar & Date.");
                return false;
            }
            final int compare = dateComparator(value, isTemporalAccessor, isDate);
            if (Integer.MAX_VALUE == compare) {
                return false;
            }
            //compare >= 0
            return compare >= 0;
        }

        /**
         * verify whether the value is a past time.
         *
         * @param value      any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
         * @param annotation the {@link Past} annotation get from the field.
         * @return if return {@code true} that the value is a past time, otherwise no.
         */
        private boolean validate(Object value, Past annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isTemporalAccessor = isTemporalAccessor(value);
            final boolean isDate = isDate(value);
            final boolean isCalendar = isCalendar(value);
            if (!isTemporalAccessor && !isDate && !isCalendar) {
                setIllegalArgMessage("The @Past only supports TemporalAccessor & Calendar & Date.");
                return false;
            }
            final int compare = dateComparator(value, isTemporalAccessor, isDate);
            if (Integer.MAX_VALUE == compare) {
                return false;
            }
            //compare < 0
            return compare < 0;
        }

        /**
         * verify whether the value is a future time or present.
         *
         * @param value      any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
         * @param annotation the {@link Future} annotation get from the field.
         * @return if return {@code true} that the value is a past time or present, otherwise no.
         */
        private boolean validate(Object value, PastOrPresent annotation) {
            if (Objects.isNull(annotation)) {
                return true;
            }
            setValidateMessage(annotation.message());
            if (Objects.isNull(value)) {
                return false;
            }
            final boolean isTemporalAccessor = isTemporalAccessor(value);
            final boolean isDate = isDate(value);
            final boolean isCalendar = isCalendar(value);
            if (!isTemporalAccessor && !isDate && !isCalendar) {
                setIllegalArgMessage("The @PastOrPresent only supports TemporalAccessor & Calendar & Date.");
                return false;
            }
            final int compare = dateComparator(value, isTemporalAccessor, isDate);
            if (Integer.MAX_VALUE == compare) {
                return false;
            }
            //compare <= 0
            return compare <= 0;
        }

        /**
         * verify the fields of the object base on the annotation present on it.
         * if it is not valid that will throw a {@link ValidateException}.
         * or a {@link IllegalArgumentException} when argument is illegal.
         * <p>
         * {@code object} any object value. with some validation annotation in {@code javax.validation.constraints.*}
         * {@code fields} the list of fields to verify. if no, that will verify all fields in the object.
         */
        private void action() {
            isNull(object, "The object to validate must not be null.");
            Field[] validateFields = filterFields(object, fields);
            for (Field validateField : validateFields) {
                //filter the validation annotation
                Annotation[] validateAnnotations = filterAnnotations(validateField);
                if (isEmpty(validateAnnotations)) continue;
                //get value.
                Object value = obtainFieldValue(object, validateField);
                for (Annotation annotation : validateAnnotations) {
                    //get method
                    Method method = lookupMethod(this.getClass(), validateMethodName, annotation.annotationType());
                    // result.
                    boolean invoke = invokeMethod(this, method, value, annotation);
                    if (invoke) continue;
                    if (logWrite) {
                        write(validateField, value, method, annotation);
                    }
                    if (1 == code) {
                        throw newValidateException(message);
                    } else {
                        throw newIllegalArgException(message);
                    }
                }
            }
        }
    }
    /**
     * get field by the names
     *
     * @param object the target object
     * @param fields the names of field
     * @return list of field
     */
    private static Field[] filterFields(final Object object, final String... fields) {
        Class<?> objectClass = object.getClass();
        // get fields, if has specify fields, otherwise get all fields on the object.
        if (isEmpty(fields)) {
            return objectClass.getDeclaredFields();
        }
        List<Field> validateFields = new LinkedList<>();
        for (String field : fields) {
            Field validateField;
            try {
                validateField = objectClass.getDeclaredField(field);
            } catch (NoSuchFieldException e) {
                throw newIllegalArgException("No such field '%s' in : %s.", field, objectClass.getCanonicalName());
            }
            validateFields.add(validateField);
        }
        //if valid fields is empty
        if (validateFields.isEmpty()) {
            throw newIllegalArgException("The specified field names did not resolve any available fields to verify.");
        }
        return validateFields.toArray(new Field[validateFields.size()]);
    }

    /**
     * get all validation annotation on the field
     *
     * @param field the target field
     * @return list of validation annotation
     */
    private static Annotation[] filterAnnotations(final Field field) {
        if (!field.isAccessible()) {
            field.setAccessible(true);
        }
        if (annotationOnFieldCache.keySet().contains(field)) {
            return annotationOnFieldCache.get(field);
        }
        //get all annotation of the field
        Annotation[] annotations = field.getDeclaredAnnotations();
        //filter the validation annotation
        if (notEmpty(annotations)) {
            annotations = Arrays.stream(annotations).filter(annotation -> annotationValidMethodCache.keySet().contains(annotation.annotationType())).toArray(Annotation[]::new);
        }
        annotationOnFieldCache.put(field, annotations);
        log.info("Get Declared annotation on field '{}', cache size: {}", field.getName(), annotationOnFieldCache.size());
        return annotations;
    }

    /**
     * get value of the field on the object
     *
     * @param object the target object
     * @param field  the field
     * @return the value
     */
    private static Object obtainFieldValue(final Object object, final Field field) {
        final Object value;
        try {
            value = field.get(object);
        } catch (IllegalAccessException e) {
            throw newIllegalArgException("Illegal Access '%s' in %s.", field.getName(), object.getClass().getCanonicalName());
        }
        return value;
    }

    private static Method lookupMethod(final Class<?> target, final String methodName, final Class<?> annotationType) {
        if (annotationValidMethodCache.keySet().contains(annotationType)) {
            return annotationValidMethodCache.get(annotationType);
        }
        final Method method;
        try {
            method = target.getDeclaredMethod(methodName, Object.class, annotationType);
        } catch (NoSuchMethodException e) {
            throw newIllegalArgException("No such method [%s(Object,%s)] in %s.", methodName, annotationType, target.getName());
        }
        if (!method.isAccessible()) {
            method.setAccessible(true);
        }
        annotationValidMethodCache.put(annotationType, method);
        return method;
    }

    /**
     * look up the {@link MethodHandle} for the target verify.
     *
     * @param lookup         {@link MethodHandles#lookup()}
     * @param methodName     the name of method
     * @param annotationType the return type and args type
     * @return MethodHandle
     */
    private static MethodHandle lookupMethod(final MethodHandles.Lookup lookup, final String methodName, final Class<?> annotationType) {
        if (annotationValidMethodHandleCache.keySet().contains(annotationType)) {
            return annotationValidMethodHandleCache.get(annotationType);
        }
        final Class<?> lookupClass = lookup.lookupClass();
        final MethodHandle methodHandle;
        final MethodType methodType = MethodType.methodType(boolean.class, Object.class, annotationType);
        try {
            methodHandle = lookup.findVirtual(lookupClass, methodName, methodType);
        } catch (NoSuchMethodException | IllegalAccessException e) {
            throw newIllegalArgException("No such method [%s(%s)] in %s.", methodName, methodType, lookupClass.getName());
        }
        annotationValidMethodHandleCache.put(annotationType, methodHandle);
        return methodHandle;
    }

    /**
     * invoke the target methodHandle to verify
     *
     * @param target       the target object
     * @param methodHandle the verify methodHandle
     * @param args         the list of args
     * @return boolean
     */
    private static boolean invokeMethod(final Object target, final MethodHandle methodHandle, final Object... args) {
        final boolean invoke;
        try {
            invoke = (boolean) methodHandle.bindTo(target).invokeWithArguments(args);
        } catch (Throwable throwable) {
            throw newIllegalArgException("Invoke the target method [%s] failed.", methodHandle);
        }
        return invoke;
    }

    /**
     * invoke the target method to verify.
     *
     * @param target     the target object
     * @param method     the target method
     * @param value      the value
     * @param annotation the annotation
     * @return boolean
     */
    private static boolean invokeMethod(final Object target, final Method method, final Object value, final Annotation annotation) {
        final boolean invoke;
        try {
            invoke = (boolean) method.invoke(target, value, annotation);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw newIllegalArgException("Invoke the target method [%s(Object,%s)] failed.", method.getName(), annotation.annotationType().getName());
        }
        return invoke;
    }
    /**
     * the format of the {@code info} level log.
     *
     * @param validateField the field to valid.
     * @param value      the value of the field.
     * @param method     the method to action
     * @param annotation the validation annotation of the field.
     */
    private static void write(Field validateField, Object value, Method method, Annotation annotation) {
        log.warn("###| FIELD       : {}", validateField);
        log.warn("###| FIELD_VALUE : {}", value);
        log.warn("###| METHOD      : (false) {}(Object,{})", method.getName(), annotation.annotationType().getName());
    }
    /**
     * create a {@link IllegalArgumentException} with the message.
     *
     * @param message the message of exception.
     * @return a {@link IllegalArgumentException}
     */
    private static IllegalArgumentException newIllegalArgException(String message, Object... values) {
        return new IllegalArgumentException(format(message, values));
    }

    /**
     * create a {@link ValidateException} with the message.
     *
     * @param message the message of exception.
     * @return a {@link ValidateException}
     */
    private static ValidateException newValidateException(String message, Object... values) {
        return new ValidateException(format(message, values));
    }

    /**
     * format the message with some values
     *
     * @param message the message string.
     * @param values  some values.
     * @return the message has format if necessary
     */
    private static String format(String message, Object... values) {
        if(message.contains("%s") && notEmpty(values)) {
            return String.format(message, values);
        }
        return message;
    }
    /**
     * verify whether the expression is {@code true}.
     *
     * @param object any object.
     * @param message    if {@code true} that throw a {@link IllegalArgumentException} with message.
     */
    private static void isNull(Object object, String message, Object... values) {
        isTrue(Objects.isNull(object), message, values);
    }

    private static void isTrue(boolean expression, String message, Object... values) {
        if (expression) {
            throw newIllegalArgException(format(message, values));
        }
    }

    /**
     * to support {@link SecurityManager}
     *
     * @param action {@link PrivilegedAction}
     * @param <T>    the type of return.
     * @return return the result.
     */
    private <T> T run(PrivilegedAction<T> action) {
        return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run();
    }
}
