1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639 |
- 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 VerifyProcessor {
- /**
- * the name of the verify method.
- */
- private static final String VerifyMethodName = "verify";
- /**
- * the list of validation annotation & method can be work in {@code javax.validation.constraints.*}
- */
- private static final Map<Class<?>, Method> ClassMethodCache = new ConcurrentHashMap<>();
- private static final Map<Class<?>, MethodHandle> ClassMethodHandleCache = new ConcurrentHashMap<>();
- private static final Map<Field, Annotation[]> FieldAnnotationCache = 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 constrctor.
- */
- private VerifyProcessor() {
- }
- /**
- * collect all vaild validation annotation from the methods.
- */
- static {
- collectValidAnnotationMethod();
- }
- private static void collectValidAnnotationMethod() {
- Method[] declaredMethods = VerifyAction.class.getDeclaredMethods();
- List<Method> verifyMethods = Arrays.stream(declaredMethods).filter(method -> VerifyMethodName.equals(method.getName())).collect(Collectors.toList());
- isTrue(verifyMethods.isEmpty(), "No any method named 'verify' in VerifyAction.");
- for (Method method : verifyMethods) {
- Optional<Class<?>> classOptional = Arrays.stream(method.getParameterTypes()).filter(Annotation.class::isAssignableFrom).findFirst();
- classOptional.ifPresent(verifyClass -> {
- if (!method.isAccessible()) {
- method.setAccessible(true);
- }
- ClassMethodCache.put(verifyClass, method);
- log.info("@interface: {}", verifyClass.getCanonicalName());
- });
- }
- isTrue(ClassMethodCache.isEmpty(), "No valid validation annotation was resolved in VerifyAction.");
- }
- /**
- * 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 perform(final boolean logWrite, final Object object, final String... fields) {
- new VerifyAction(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 perform(final Object object, final String... fields) {
- new VerifyAction(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)) {
- Double v = (Double) value;
- OptionalInt infinity = infinityCheck(v, treatNanAs);
- if (infinity.isPresent()) {
- return infinity.getAsInt();
- } else {
- return val.compareTo(bounds);
- }
- }
- if (isFloat(value)) {
- Float v = (Float) value;
- OptionalInt infinity = infinityCheck(v, 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<?> iter = (Iterator<?>) value;
- count = 0;
- while (iter.hasNext()) {
- count++;
- iter.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 VerifyAction {
- private String message;
- private int code;
- private boolean logWrite = true;
- private Object object;
- private String[] fields;
- private VerifyAction(boolean logWrite, Object object, String... fields) {
- this.logWrite = logWrite;
- this.object = object;
- this.fields = fields;
- }
- private VerifyAction(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 setVerifyMessage(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 verify(Object value, NotNull annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Null annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, AssertTrue annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, AssertFalse annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, DecimalMax annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, DecimalMin annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, NotEmpty annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(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));
- }
- }
- setVerifyMessage(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 verify(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));
- }
- }
- setVerifyMessage(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 verify(Object value, NotBlank annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Email annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Pattern annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Max annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 ture} that the value is less than or equal the minimum, otherwise no.
- */
- private boolean verify(Object value, Min annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Negative annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, NegativeOrZero annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Positive annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, PositiveOrZero annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Future annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, FutureOrPresent annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, Past annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 verify(Object value, PastOrPresent annotation) {
- if (Objects.isNull(annotation)) {
- return true;
- }
- setVerifyMessage(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 VerifyException}.
- * 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 verify must not be null.");
- Field[] verifyFields = filterFields(object, fields);
- for (Field verifyField : verifyFields) {
- //filter the validation annotation
- Annotation[] verifyAnnos = filterAnnotations(verifyField);
- if (isEmpty(verifyAnnos)) continue;
- //get value.
- Object value = obtainFieldValue(object, verifyField);
- for (Annotation verifyAnno : verifyAnnos) {
- //get method
- Method method = lookupMethod(this.getClass(), VerifyMethodName, verifyAnno.annotationType());
- // result.
- boolean invoke = invokeMethod(this, method, value, verifyAnno);
- if (invoke) continue;
- if (logWrite) {
- write(verifyField, value, method, verifyAnno);
- }
- if (1 == code) {
- throw newVerifyException(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> verifyFields = new ArrayList<>();
- for (String field : fields) {
- Field verifyField;
- try {
- verifyField = objectClass.getDeclaredField(field);
- } catch (NoSuchFieldException e) {
- throw newIllegalArgException("No such field '%s' in : %s.", field, objectClass.getCanonicalName());
- }
- verifyFields.add(verifyField);
- }
- //if vaild fields is empty
- if (verifyFields.isEmpty()) {
- throw newIllegalArgException("The specified field names did not resolve any available fields to verify.");
- }
- return verifyFields.toArray(new Field[verifyFields.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 (FieldAnnotationCache.keySet().contains(field)) {
- return FieldAnnotationCache.get(field);
- }
- //get all annotation of the field
- Annotation[] annotations = field.getDeclaredAnnotations();
- //filter the validation annotation
- if (notEmpty(annotations)) {
- List<Annotation> verifyAnnos = Arrays.stream(annotations).filter(anno -> ClassMethodCache.keySet().contains(anno.annotationType())).collect(Collectors.toList());
- annotations = verifyAnnos.toArray(new Annotation[verifyAnnos.size()]);
- }
- FieldAnnotationCache.put(field, annotations);
- log.info("Get Declared annotation on field '{}', cache size: {}", field.getName(), FieldAnnotationCache.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 (ClassMethodCache.keySet().contains(annotationType)) {
- return ClassMethodCache.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);
- }
- ClassMethodCache.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 (ClassMethodHandleCache.keySet().contains(annotationType)) {
- return ClassMethodHandleCache.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());
- }
- ClassMethodHandleCache.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 verifyField the field to valid.
- * @param value the value of the field.
- * @param method the method to action
- * @param verifyAnno the validation annotation of the field.
- */
- private static void write(Field verifyField, Object value, Method method, Annotation verifyAnno) {
- log.warn("###| FIELD : {}", verifyField);
- log.warn("###| FIELD_VALUE : {}", value);
- log.warn("###| METHOD : (false) {}(Object,{})", method.getName(), verifyAnno.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 VerifyException} with the message.
- *
- * @param message the message of exception.
- * @return a {@link VerifyException}
- */
- private static VerifyException newVerifyException(String message, Object... values) {
- return new VerifyException(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") && null != values && values.length > 0) {
- 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) {
- if (null == object) {
- throw newIllegalArgException(message);
- }
- }
- private static void isTrue(boolean expression, String message) {
- if (expression) {
- throw newIllegalArgException(message);
- }
- }
- /**
- * 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();
- }
- }
|