VerifyProcessor.java 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639
  1. package cn.com.ty.lift.common.verify;
  2. import lombok.extern.slf4j.Slf4j;
  3. import javax.validation.constraints.*;
  4. import java.lang.annotation.Annotation;
  5. import java.lang.invoke.MethodHandle;
  6. import java.lang.invoke.MethodHandles;
  7. import java.lang.invoke.MethodType;
  8. import java.lang.reflect.Array;
  9. import java.lang.reflect.Field;
  10. import java.lang.reflect.InvocationTargetException;
  11. import java.lang.reflect.Method;
  12. import java.math.BigDecimal;
  13. import java.math.BigInteger;
  14. import java.net.IDN;
  15. import java.security.AccessController;
  16. import java.security.PrivilegedAction;
  17. import java.time.*;
  18. import java.time.chrono.HijrahDate;
  19. import java.time.chrono.JapaneseDate;
  20. import java.time.chrono.MinguoDate;
  21. import java.time.chrono.ThaiBuddhistDate;
  22. import java.time.temporal.TemporalAccessor;
  23. import java.util.*;
  24. import java.util.concurrent.ConcurrentHashMap;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.PatternSyntaxException;
  27. import java.util.stream.Collectors;
  28. import static java.util.regex.Pattern.CASE_INSENSITIVE;
  29. /**
  30. * the validation for parameter implements {@linkplain javax.validation.constraints},
  31. * reform from hibernate validator (v6.0.16.Final)
  32. *
  33. * @author wcz
  34. * @since 2020/2/15
  35. */
  36. @Slf4j
  37. public class VerifyProcessor {
  38. /**
  39. * the name of the verify method.
  40. */
  41. private static final String VerifyMethodName = "verify";
  42. /**
  43. * the list of validation annotation & method can be work in {@code javax.validation.constraints.*}
  44. */
  45. private static final Map<Class<?>, Method> ClassMethodCache = new ConcurrentHashMap<>();
  46. private static final Map<Class<?>, MethodHandle> ClassMethodHandleCache = new ConcurrentHashMap<>();
  47. private static final Map<Field, Annotation[]> FieldAnnotationCache = new ConcurrentHashMap<>();
  48. /**
  49. * the max length for a valid email address local part.
  50. */
  51. private static final int MAX_LOCAL_PART_LENGTH = 64;
  52. /**
  53. * the regular expression for local part of a valid email address.
  54. */
  55. private static final String LOCAL_PART_ATOM = "[a-z0-9!#$%&'*+/=?^_`{|}~\u0080-\uFFFF-]";
  56. /**
  57. * the regular expression for the local part of an email address.
  58. */
  59. private static final String LOCAL_PART_INSIDE_QUOTES_ATOM = "([a-z0-9!#$%&'*.(),<>\\[\\]:; @+/=?^_`{|}~\u0080-\uFFFF-]|\\\\\\\\|\\\\\\\")";
  60. /**
  61. * Regular expression for the local part of an email address (everything before '@')
  62. */
  63. private static final java.util.regex.Pattern LOCAL_PART_PATTERN = java.util.regex.Pattern.compile(
  64. "(" + LOCAL_PART_ATOM + "+|\"" + LOCAL_PART_INSIDE_QUOTES_ATOM + "+\")" +
  65. "(\\." + "(" + LOCAL_PART_ATOM + "+|\"" + LOCAL_PART_INSIDE_QUOTES_ATOM + "+\")" + ")*", CASE_INSENSITIVE
  66. );
  67. /**
  68. * This is the maximum length of a domain name. But be aware that each label (parts separated by a dot) of the
  69. * domain name must be at most 63 characters long. This is verified by {@link IDN#toASCII(String)}.
  70. */
  71. private static final int MAX_DOMAIN_PART_LENGTH = 255;
  72. private static final String DOMAIN_CHARS_WITHOUT_DASH = "[a-z\u0080-\uFFFF0-9!#$%&'*+/=?^_`{|}~]";
  73. private static final String DOMAIN_LABEL = "(" + DOMAIN_CHARS_WITHOUT_DASH + "-*)*" + DOMAIN_CHARS_WITHOUT_DASH + "+";
  74. /**
  75. * the regex for check domain
  76. */
  77. private static final String DOMAIN = DOMAIN_LABEL + "+(\\." + DOMAIN_LABEL + "+)*";
  78. /**
  79. * regex for check IP v4
  80. */
  81. private static final String IP_DOMAIN = "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}";
  82. /**
  83. * IP v6 regex taken from http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
  84. */
  85. 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]))";
  86. /**
  87. * Regular expression for the domain part of an URL
  88. * A host string must be a domain string, an IPv4 address string, or "[", followed by an IPv6 address string, followed by "]".
  89. */
  90. private static final java.util.regex.Pattern DOMAIN_PATTERN = java.util.regex.Pattern.compile(DOMAIN + "|\\[" + IP_V6_DOMAIN + "\\]", CASE_INSENSITIVE);
  91. /**
  92. * Regular expression for the domain part of an email address (everything after '@')
  93. */
  94. private static final java.util.regex.Pattern EMAIL_DOMAIN_PATTERN = java.util.regex.Pattern.compile(
  95. DOMAIN + "|\\[" + IP_DOMAIN + "\\]|" + "\\[IPv6:" + IP_V6_DOMAIN + "\\]", CASE_INSENSITIVE
  96. );
  97. /**
  98. * the minimum value of compare two number for the infinity check when double or float.
  99. */
  100. private static final OptionalInt LESS_THAN = OptionalInt.of(-1);
  101. /**
  102. * the empty OptionalInt(value = 0) of compare two number for the infinity check when double or float.
  103. */
  104. private static final OptionalInt FINITE_VALUE = OptionalInt.empty();
  105. /**
  106. * the maximun value of compare two number for the infinity check when double or float.
  107. */
  108. private static final OptionalInt GREATER_THAN = OptionalInt.of(1);
  109. /**
  110. * short 0
  111. */
  112. private static final short SHORT_ZERO = (short) 0;
  113. /**
  114. * byte 0
  115. */
  116. private static final byte BYTE_ZERO = (byte) 0;
  117. /**
  118. * to private the constrctor.
  119. */
  120. private VerifyProcessor() {
  121. }
  122. /**
  123. * collect all vaild validation annotation from the methods.
  124. */
  125. static {
  126. collectValidAnnotationMethod();
  127. }
  128. private static void collectValidAnnotationMethod() {
  129. Method[] declaredMethods = VerifyAction.class.getDeclaredMethods();
  130. List<Method> verifyMethods = Arrays.stream(declaredMethods).filter(method -> VerifyMethodName.equals(method.getName())).collect(Collectors.toList());
  131. isTrue(verifyMethods.isEmpty(), "No any method named 'verify' in VerifyAction.");
  132. for (Method method : verifyMethods) {
  133. Optional<Class<?>> classOptional = Arrays.stream(method.getParameterTypes()).filter(Annotation.class::isAssignableFrom).findFirst();
  134. classOptional.ifPresent(verifyClass -> {
  135. if (!method.isAccessible()) {
  136. method.setAccessible(true);
  137. }
  138. ClassMethodCache.put(verifyClass, method);
  139. log.info("@interface: {}", verifyClass.getCanonicalName());
  140. });
  141. }
  142. isTrue(ClassMethodCache.isEmpty(), "No valid validation annotation was resolved in VerifyAction.");
  143. }
  144. /**
  145. * to do verify by using the singleton instance.
  146. *
  147. * @param logWrite {@code true} log information, otherwise no log to file.
  148. * @param object the target object to verify.
  149. * @param fields the list of fields to verify.
  150. */
  151. public static void perform(final boolean logWrite, final Object object, final String... fields) {
  152. new VerifyAction(logWrite, object, fields).action();
  153. }
  154. /**
  155. * to do verify by using the singleton instance.
  156. *
  157. * @param object the target object to verify.
  158. * @param fields the list of fields to verify.
  159. */
  160. public static void perform(final Object object, final String... fields) {
  161. new VerifyAction(object, fields).action();
  162. }
  163. /**
  164. * get the systemDefaultZone {@link Clock#systemDefaultZone()}. for the date compare.
  165. */
  166. private static Clock systemDefaultClock() {
  167. return Clock.offset(Clock.systemDefaultZone(), Duration.ZERO.abs().negated());
  168. }
  169. private static <T> boolean isEmpty(T[] array) {
  170. return array == null || array.length == 0;
  171. }
  172. private static <T> boolean notEmpty(T[] array) {
  173. return array != null && array.length > 0;
  174. }
  175. private static boolean notBlank(String value) {
  176. return null != value && !value.trim().isEmpty();
  177. }
  178. /**
  179. * verify the value is instance of the Number.
  180. *
  181. * @param value any value.
  182. * @return if return {@code true}, the value is Number, otherwise no
  183. */
  184. private static boolean isNumber(Object value) {
  185. return (value instanceof Number);
  186. }
  187. /**
  188. * verify the value is instance of the CharSequence.
  189. *
  190. * @param value any value.
  191. * @return if return {@code true}, the value is CharSequence, otherwise no
  192. */
  193. private static boolean isCharSequence(Object value) {
  194. return (value instanceof CharSequence);
  195. }
  196. /**
  197. * verify the value is instance of the BigDecimal.
  198. *
  199. * @param value any value.
  200. * @return if return {@code true}, the value is BigDecimal, otherwise no
  201. */
  202. private static boolean isBigDecimal(Object value) {
  203. return (value instanceof BigDecimal);
  204. }
  205. /**
  206. * verify the value is instance of the BigInteger.
  207. *
  208. * @param value any value.
  209. * @return if return {@code true}, the value is BigInteger, otherwise no
  210. */
  211. private static boolean isBigInteger(Object value) {
  212. return (value instanceof BigInteger);
  213. }
  214. /**
  215. * verify the value is instance of the Long.
  216. *
  217. * @param value any value.
  218. * @return if return {@code true}, the value is Long, otherwise no
  219. */
  220. private static boolean isLong(Object value) {
  221. return (value instanceof Long);
  222. }
  223. /**
  224. * verify the value is instance of the Double.
  225. *
  226. * @param value any value.
  227. * @return if return {@code true}, the value is Double, otherwise no
  228. */
  229. private static boolean isDouble(Object value) {
  230. return (value instanceof Double);
  231. }
  232. /**
  233. * verify the value is instance of the Float.
  234. *
  235. * @param value any value.
  236. * @return if return {@code true}, the value is Float, otherwise no
  237. */
  238. private static boolean isFloat(Object value) {
  239. return (value instanceof Float);
  240. }
  241. /**
  242. * verify the value is instance of the TemporalAccessor(java 8).
  243. *
  244. * @param value any value.
  245. * @return if return {@code true}, the value is TemporalAccessor, otherwise no
  246. */
  247. private static boolean isTemporalAccessor(Object value) {
  248. return (value instanceof TemporalAccessor);
  249. }
  250. /**
  251. * verify the value is instance of the java.util.Date.
  252. *
  253. * @param value any value.
  254. * @return if return {@code true}, the value is Date, otherwise no
  255. */
  256. private static boolean isDate(Object value) {
  257. return (value instanceof Date);
  258. }
  259. /**
  260. * verify the value is instance of the java.util.Calendar.
  261. *
  262. * @param value any value.
  263. * @return if return true, the value is Calendar, otherwise no
  264. */
  265. private static boolean isCalendar(Object value) {
  266. return (value instanceof Calendar);
  267. }
  268. /**
  269. * verify whether a double value is infinity with less or greater.
  270. *
  271. * @param number any double value.
  272. * @param treatNanAs when the value isn't a number, return this value.
  273. * @return a OptionalInt value with the result of compare to infinity.
  274. * -1 for equal {@link Double#NEGATIVE_INFINITY}
  275. * 1 for equal {@link Double#POSITIVE_INFINITY}
  276. */
  277. private static OptionalInt infinityCheck(Double number, OptionalInt treatNanAs) {
  278. if (number == Double.NEGATIVE_INFINITY) {
  279. return LESS_THAN;
  280. } else if (number.isNaN()) {
  281. return treatNanAs;
  282. } else if (number == Double.POSITIVE_INFINITY) {
  283. return GREATER_THAN;
  284. } else {
  285. return FINITE_VALUE;
  286. }
  287. }
  288. /**
  289. * verify whether a float vlaue is infinity with less or greater.
  290. *
  291. * @param number any float value.
  292. * @param treatNanAs when the value isn't a number, return this value.
  293. * @return a OptionalInt value with the result of compare to infinity.
  294. * -1 for equal {@link Float#NEGATIVE_INFINITY}
  295. * 1 for equal {@link Float#POSITIVE_INFINITY}
  296. */
  297. private static OptionalInt infinityCheck(Float number, OptionalInt treatNanAs) {
  298. if (number == Float.NEGATIVE_INFINITY) {
  299. return LESS_THAN;
  300. } else if (number.isNaN()) {
  301. return treatNanAs;
  302. } else if (number == Float.POSITIVE_INFINITY) {
  303. return GREATER_THAN;
  304. } else {
  305. return FINITE_VALUE;
  306. }
  307. }
  308. /**
  309. * compare two bigdecimal value
  310. *
  311. * @param value any object value
  312. * @param val the bigdecimal value transform from the object value
  313. * @param bounds the boundary of the maximum or minimum
  314. * @param treatNanAs return for the value isn't number when check for infinity
  315. * @return return {@code -1} for the val is less than boundary, {@code 0} for equal,{@code 1} for greater than boundary.
  316. */
  317. private static int decimalComparator(Object value, BigDecimal val, BigDecimal bounds, OptionalInt treatNanAs) {
  318. if (isLong(value) || isBigInteger(value) || isBigDecimal(value)) {
  319. return val.compareTo(bounds);
  320. }
  321. if (isDouble(value)) {
  322. Double v = (Double) value;
  323. OptionalInt infinity = infinityCheck(v, treatNanAs);
  324. if (infinity.isPresent()) {
  325. return infinity.getAsInt();
  326. } else {
  327. return val.compareTo(bounds);
  328. }
  329. }
  330. if (isFloat(value)) {
  331. Float v = (Float) value;
  332. OptionalInt infinity = infinityCheck(v, treatNanAs);
  333. if (infinity.isPresent()) {
  334. return infinity.getAsInt();
  335. } else {
  336. return val.compareTo(bounds);
  337. }
  338. }
  339. return val.compareTo(bounds);
  340. }
  341. /**
  342. * get the length or size of the value.
  343. *
  344. * @param value any object value
  345. * @return the length or size of the value when it's working, but return zero when the value is null.
  346. */
  347. private static int length(Object value) {
  348. if (Objects.isNull(value)) {
  349. return 0;
  350. }
  351. if (isCharSequence(value)) {
  352. return ((CharSequence) value).length();
  353. }
  354. if (value instanceof Collection) {
  355. return ((Collection<?>) value).size();
  356. }
  357. if (value instanceof Map) {
  358. return ((Map<?, ?>) value).size();
  359. }
  360. if (value.getClass().isArray()) {
  361. return Array.getLength(value);
  362. }
  363. int count;
  364. if (value instanceof Iterator) {
  365. Iterator<?> iter = (Iterator<?>) value;
  366. count = 0;
  367. while (iter.hasNext()) {
  368. count++;
  369. iter.next();
  370. }
  371. return count;
  372. }
  373. if (value instanceof Enumeration) {
  374. Enumeration<?> enumeration = (Enumeration<?>) value;
  375. count = 0;
  376. while (enumeration.hasMoreElements()) {
  377. count++;
  378. enumeration.nextElement();
  379. }
  380. return count;
  381. }
  382. return -1;
  383. }
  384. /**
  385. * compare two number value with less than or greater than.
  386. *
  387. * @param value any number value
  388. * @param bounds the other value
  389. * @param treatNanAs return when the value is {@code NaN}.
  390. * @return {@code -1} value less than limit, {@code 0} value equal limit, {@code 1} value greater than limit.
  391. */
  392. private static int numberComparator(Number value, long bounds, OptionalInt treatNanAs) {
  393. if (isLong(value)) {
  394. return ((Long) value).compareTo(bounds);
  395. }
  396. if (isDouble(value)) {
  397. Double val = (Double) value;
  398. OptionalInt infinity = infinityCheck(val, treatNanAs);
  399. if (infinity.isPresent()) {
  400. return infinity.getAsInt();
  401. } else {
  402. return Double.compare(val, bounds);
  403. }
  404. }
  405. if (isFloat(value)) {
  406. Float val = (Float) value;
  407. OptionalInt infinity = infinityCheck(val, treatNanAs);
  408. if (infinity.isPresent()) {
  409. return infinity.getAsInt();
  410. } else {
  411. return Float.compare(val, bounds);
  412. }
  413. }
  414. if (isBigDecimal(value)) {
  415. return ((BigDecimal) value).compareTo(BigDecimal.valueOf(bounds));
  416. }
  417. if (isBigInteger(value)) {
  418. return ((BigInteger) value).compareTo(BigInteger.valueOf(bounds));
  419. }
  420. return Long.compare(value.longValue(), bounds);
  421. }
  422. /**
  423. * get the sign number of the value.
  424. *
  425. * @param value any number value
  426. * @param treatNanAs return when the value equal infinity.
  427. * @return {@code -1} the value less than zero, {@code 0} the value equal zero, {@code 1} the value greater than zero.
  428. */
  429. private static int signum(Number value, OptionalInt treatNanAs) {
  430. if (isLong(value)) {
  431. return Long.signum((Long) value);
  432. }
  433. if (value instanceof Integer) {
  434. return Integer.signum((Integer) value);
  435. }
  436. if (isBigDecimal(value)) {
  437. return ((BigDecimal) value).signum();
  438. }
  439. if (isBigInteger(value)) {
  440. return ((BigInteger) value).signum();
  441. }
  442. if (isDouble(value)) {
  443. Double val = (Double) value;
  444. OptionalInt infinity = infinityCheck(val, treatNanAs);
  445. if (infinity.isPresent()) {
  446. return infinity.getAsInt();
  447. } else {
  448. return val.compareTo(0D);
  449. }
  450. }
  451. if (isFloat(value)) {
  452. Float val = (Float) value;
  453. OptionalInt infinity = infinityCheck(val, treatNanAs);
  454. if (infinity.isPresent()) {
  455. return infinity.getAsInt();
  456. } else {
  457. return val.compareTo(0F);
  458. }
  459. }
  460. if (value instanceof Byte) {
  461. return ((Byte) value).compareTo(BYTE_ZERO);
  462. }
  463. if (value instanceof Short) {
  464. return ((Short) value).compareTo(SHORT_ZERO);
  465. }
  466. return Double.compare(value.doubleValue(), 0D);
  467. }
  468. /**
  469. * the implements verify method for validation annotation in {@linkplain javax.validation.constraints}
  470. */
  471. private static class VerifyAction {
  472. private String message;
  473. private int code;
  474. private boolean logWrite = true;
  475. private Object object;
  476. private String[] fields;
  477. private VerifyAction(boolean logWrite, Object object, String... fields) {
  478. this.logWrite = logWrite;
  479. this.object = object;
  480. this.fields = fields;
  481. }
  482. private VerifyAction(Object object, String... fields) {
  483. this.object = object;
  484. this.fields = fields;
  485. }
  486. /**
  487. * new a BigDecimal object with a CharSequence value.
  488. *
  489. * @param value any CharSequence value
  490. * @return a new {@link BigDecimal} object or null when occur any exception.
  491. */
  492. private BigDecimal newBigDecimal(CharSequence value) {
  493. try {
  494. BigDecimal bd;
  495. if (value instanceof String) {
  496. bd = new BigDecimal((String) value);
  497. } else {
  498. bd = new BigDecimal(value.toString());
  499. }
  500. return bd;
  501. } catch (Exception e) {
  502. setIllegalArgMessage("Failed to convert '%s' to a valid BigDecimal. Exception: %s", value, e.getMessage());
  503. return null;
  504. }
  505. }
  506. /**
  507. * new a BigDecimal object with a Number value, base on the Specific type of the value.
  508. *
  509. * @param value any Number value
  510. * @return a new {@link BigDecimal} object or null when occur any exception.
  511. */
  512. private BigDecimal newBigDecimal(Number value) {
  513. try {
  514. BigDecimal bd;
  515. if (isLong(value)) {
  516. bd = BigDecimal.valueOf((Long) value);
  517. } else if (isBigDecimal(value)) {
  518. bd = ((BigDecimal) value);
  519. } else if (isBigInteger(value)) {
  520. bd = new BigDecimal((BigInteger) value);
  521. } else if (isDouble(value)) {
  522. bd = BigDecimal.valueOf((Double) value);
  523. } else if (isFloat(value)) {
  524. bd = BigDecimal.valueOf((Float) value);
  525. } else {
  526. bd = BigDecimal.valueOf(value.doubleValue());
  527. }
  528. return bd;
  529. } catch (Exception e) {
  530. setIllegalArgMessage("Failed to convert '%s' to a valid BigDecimal. Exception: %s", value, e.getMessage());
  531. return null;
  532. }
  533. }
  534. /**
  535. * set the verify message in the ThreadLocal.(code : 1)
  536. */
  537. private void setVerifyMessage(String message, Object... values) {
  538. this.code = 1;
  539. this.message = format(message, values);
  540. }
  541. /**
  542. * set the illegal argument message in the ThreadLocal.(code : 2)
  543. */
  544. private void setIllegalArgMessage(String message, Object... values) {
  545. this.code = 2;
  546. this.message = format(message, values);
  547. }
  548. /**
  549. * verify whether the value isn't {@code null}.
  550. *
  551. * @param value any object value.
  552. * @param annotation the {@link NotNull} annotation get from the field.
  553. * @return if return {@code true} that the value isn't null, otherwise no.
  554. */
  555. private boolean verify(Object value, NotNull annotation) {
  556. if (Objects.isNull(annotation)) {
  557. return true;
  558. }
  559. setVerifyMessage(annotation.message());
  560. return null != value;
  561. }
  562. /**
  563. * verify whether the value is {@code null}.
  564. *
  565. * @param value any object value.
  566. * @param annotation the {@link Null} annotation get from the field.
  567. * @return if return {@code true} that the value is null, otherwise no.
  568. */
  569. private boolean verify(Object value, Null annotation) {
  570. if (Objects.isNull(annotation)) {
  571. return true;
  572. }
  573. setVerifyMessage(annotation.message());
  574. return Objects.isNull(value);
  575. }
  576. /**
  577. * verify whether the value is {@code true}.
  578. *
  579. * @param value any object value, but only work for {@link Boolean}
  580. * @param annotation the {@link AssertTrue} annotation get from the field.
  581. * @return if return {@code true} that the value is true, otherwise no.
  582. */
  583. private boolean verify(Object value, AssertTrue annotation) {
  584. if (Objects.isNull(annotation)) {
  585. return true;
  586. }
  587. setVerifyMessage(annotation.message());
  588. if (Objects.isNull(value)) {
  589. return false;
  590. }
  591. if (value instanceof Boolean) {
  592. return ((Boolean) value);
  593. } else {
  594. setIllegalArgMessage("The @AssertTrue only supports Boolean.");
  595. return false;
  596. }
  597. }
  598. /**
  599. * verify whether the value is {@code false}.
  600. *
  601. * @param value any object value, but only work for {@link Boolean}
  602. * @param annotation the {@link AssertFalse} annotation get from the field.
  603. * @return if return {@code true} that the value is false, otherwise no.
  604. */
  605. private boolean verify(Object value, AssertFalse annotation) {
  606. if (Objects.isNull(annotation)) {
  607. return true;
  608. }
  609. setVerifyMessage(annotation.message());
  610. if (Objects.isNull(value)) {
  611. return false;
  612. }
  613. if (value instanceof Boolean) {
  614. return !((Boolean) value);
  615. } else {
  616. setIllegalArgMessage("The @AssertFalse only supports Boolean.");
  617. return false;
  618. }
  619. }
  620. /**
  621. * verify whether the value is less than the bigdecimal maximum value.
  622. *
  623. * @param value any object value, but only work for {@link Number} and {@link CharSequence}
  624. * @param annotation the {@link DecimalMax} annotation get from the field.
  625. * @return if return {@code true} that the value is less than the bigdecimal maximum, otherwise no.
  626. */
  627. private boolean verify(Object value, DecimalMax annotation) {
  628. if (Objects.isNull(annotation)) {
  629. return true;
  630. }
  631. setVerifyMessage(annotation.message());
  632. if (Objects.isNull(value)) {
  633. return false;
  634. }
  635. final boolean isNumber = isNumber(value);
  636. final boolean isCharSequence = isCharSequence(value);
  637. if (!isNumber && !isCharSequence) {
  638. setIllegalArgMessage("The @DecimalMax only supports Number & CharSequence.");
  639. return false;
  640. }
  641. final String maxValue = annotation.value();
  642. if (Objects.isNull(maxValue)) {
  643. setIllegalArgMessage("The value of @DecimalMax is null, a invalid BigDecimal format.");
  644. return false;
  645. }
  646. final BigDecimal max = newBigDecimal(maxValue);
  647. if (Objects.isNull(max)) {
  648. return false;
  649. }
  650. final BigDecimal val;
  651. if (isNumber) {
  652. val = newBigDecimal((Number) value);
  653. } else {
  654. val = newBigDecimal((CharSequence) value);
  655. }
  656. if (Objects.isNull(val)) {
  657. return false;
  658. }
  659. final int compare = decimalComparator(value, val, max, GREATER_THAN);
  660. final boolean inclusive = annotation.inclusive();
  661. //inclusive ? comparisonResult <= 0 : comparisonResult < 0;
  662. if (inclusive) {
  663. return compare <= 0;
  664. } else {
  665. return compare < 0;
  666. }
  667. }
  668. /**
  669. * verify whether the value is greater than the bigdecimal minimum value.
  670. *
  671. * @param value any object value, but only work for {@link Number} and {@link CharSequence}
  672. * @param annotation the {@link DecimalMin} annotation get from the field.
  673. * @return if return {@code true} that the value is greater than the bigdecimal minimum, otherwise no.
  674. */
  675. private boolean verify(Object value, DecimalMin annotation) {
  676. if (Objects.isNull(annotation)) {
  677. return true;
  678. }
  679. setVerifyMessage(annotation.message());
  680. if (Objects.isNull(value)) {
  681. return false;
  682. }
  683. final boolean isNumber = isNumber(value);
  684. final boolean isCharSequence = isCharSequence(value);
  685. if (!isNumber && !isCharSequence) {
  686. setIllegalArgMessage("The @DecimalMin only supports Number & CharSequence.");
  687. return false;
  688. }
  689. final String minValue = annotation.value();
  690. if (Objects.isNull(minValue)) {
  691. setIllegalArgMessage("The value of @DecimalMin is null, a invalid BigDecimal format.");
  692. return false;
  693. }
  694. final BigDecimal min = newBigDecimal(minValue);
  695. if (Objects.isNull(min)) {
  696. return false;
  697. }
  698. final BigDecimal val;
  699. if (isNumber) {
  700. val = newBigDecimal((Number) value);
  701. } else {
  702. val = newBigDecimal((CharSequence) value);
  703. }
  704. if (Objects.isNull(val)) {
  705. return false;
  706. }
  707. final int compare = decimalComparator(value, val, min, LESS_THAN);
  708. final boolean inclusive = annotation.inclusive();
  709. //inclusive ? comparisonResult >= 0 : comparisonResult > 0;
  710. if (inclusive) {
  711. return compare >= 0;
  712. } else {
  713. return compare > 0;
  714. }
  715. }
  716. /**
  717. * verify whether the value isn't null and not empty.
  718. *
  719. * @param value any object value,but only work for {@link CharSequence},{@link Collection},{@link Map},{@link Array},{@link Iterator}and {@link Enumeration}
  720. * @param annotation the {@link NotEmpty} annotation get from the field.
  721. * @return if return {@code true} that the value isn't empty(with some length or size), otherwise no.
  722. */
  723. private boolean verify(Object value, NotEmpty annotation) {
  724. if (Objects.isNull(annotation)) {
  725. return true;
  726. }
  727. setVerifyMessage(annotation.message());
  728. if (Objects.isNull(value)) {
  729. return false;
  730. }
  731. final int length = length(value);
  732. if (-1 == length) {
  733. setIllegalArgMessage("The @NotEmpty only supports CharSequence & Collection & Map & Array & Iterator & Enumeration.");
  734. return false;
  735. }
  736. // length > 0
  737. return length > 0;
  738. }
  739. /**
  740. * verify whether the value satisfy the size with maximum and minimun.
  741. *
  742. * @param value any object value, but only work for {@link CharSequence},{@link Collection},{@link Map},{@link Array},{@link Iterator}and {@link Enumeration}
  743. * @param annotation the {@link Size} annotation get from the field.
  744. * @return if return {@code true} that the value satisfy the size, otherwise no.
  745. */
  746. private boolean verify(Object value, Size annotation) {
  747. if (Objects.isNull(annotation)) {
  748. return true;
  749. }
  750. final int min = annotation.min();
  751. final int max = annotation.max();
  752. String message = annotation.message();
  753. if (notBlank(message)) {
  754. if (message.contains("{min}")) {
  755. message = message.replace("{min}", String.valueOf(min));
  756. }
  757. if (message.contains("{max}")) {
  758. message = message.replace("{max}", String.valueOf(max));
  759. }
  760. }
  761. setVerifyMessage(message);
  762. if (Objects.isNull(value)) {
  763. return false;
  764. }
  765. if (min < 0) {
  766. setIllegalArgMessage("The min (%s) parameter of @Size cannot be negative.", min);
  767. return false;
  768. }
  769. if (max < 0) {
  770. setIllegalArgMessage("The max (%s) parameter of @Size cannot be negative.", max);
  771. return false;
  772. }
  773. if (min > max) {
  774. setIllegalArgMessage("The min (%s) must be less or equals max (%s) of @Size.", min, max);
  775. return false;
  776. }
  777. final int length = length(value);
  778. if (-1 == length) {
  779. setIllegalArgMessage("The @Size only supports CharSequence & Collection & Map & Array & Iterator & Enumeration.");
  780. return false;
  781. }
  782. //size >= min && size <= max
  783. return length >= min && length <= max;
  784. }
  785. /**
  786. * verify that the {@code Number} being validated matches the pattern
  787. *
  788. * @param value any object value, but only work for {@link CharSequence} and {@link Number}
  789. * @param annotation the {@link Digits} annotation get from the field.
  790. * @return if return {@code true} that the value is matches pattern, otherwise no.
  791. */
  792. private boolean verify(Object value, Digits annotation) {
  793. if (Objects.isNull(annotation)) {
  794. return true;
  795. }
  796. final int maxInteger = annotation.integer();
  797. final int maxFraction = annotation.fraction();
  798. String message = annotation.message();
  799. if (notBlank(message)) {
  800. if (message.contains("{integer}")) {
  801. message = message.replace("{integer}", String.valueOf(maxInteger));
  802. }
  803. if (message.contains("{fraction}")) {
  804. message = message.replace("{fraction}", String.valueOf(maxFraction));
  805. }
  806. }
  807. setVerifyMessage(message);
  808. if (Objects.isNull(value)) {
  809. return false;
  810. }
  811. final boolean isNumber = isNumber(value);
  812. final boolean isCharSequence = isCharSequence(value);
  813. if (!isNumber && !isCharSequence) {
  814. setIllegalArgMessage("The @Digits only supports Number & CharSequence.");
  815. return false;
  816. }
  817. if (maxInteger < 0) {
  818. setIllegalArgMessage("The length of the integer '%s' part of @Digits cannot be negative.", maxInteger);
  819. return false;
  820. }
  821. if (maxFraction < 0) {
  822. setIllegalArgMessage("The length of the fraction '%s' part of @Digits cannot be negative.", maxFraction);
  823. return false;
  824. }
  825. BigDecimal val;
  826. if (isNumber) {
  827. if (isBigDecimal(value)) {
  828. val = (BigDecimal) value;
  829. } else {
  830. val = newBigDecimal(value.toString());
  831. if (Objects.isNull(val)) {
  832. return false;
  833. }
  834. val = val.stripTrailingZeros();
  835. }
  836. } else {
  837. val = newBigDecimal((CharSequence) value);
  838. }
  839. if (Objects.isNull(val)) {
  840. return false;
  841. }
  842. int integerPart = val.precision() - val.scale();
  843. int fractionPart = val.scale() < 0 ? 0 : val.scale();
  844. //maxInteger >= integerPart && maxFraction >= fractionPart
  845. return maxInteger >= integerPart && maxFraction >= fractionPart;
  846. }
  847. /**
  848. * verify that a character sequence is not {@code null} nor empty after removing any leading or trailing whitespace.
  849. *
  850. * @param value any object value, but only work for {@link CharSequence}
  851. * @param annotation the {@link NotBlank} annotation get from the field.
  852. * @return if return {@code true} that the value isn't blank, otherwise no.
  853. */
  854. private boolean verify(Object value, NotBlank annotation) {
  855. if (Objects.isNull(annotation)) {
  856. return true;
  857. }
  858. setVerifyMessage(annotation.message());
  859. if (Objects.isNull(value)) {
  860. return false;
  861. }
  862. if (!isCharSequence(value)) {
  863. setIllegalArgMessage("The @NotBlank only supports CharSequence.");
  864. return false;
  865. }
  866. return ((CharSequence) value).toString().trim().length() > 0;
  867. }
  868. /**
  869. * verify whether the value is a valid email address.
  870. *
  871. * @param value any object value, but only work for {@link CharSequence}
  872. * @param annotation the {@link Email} annotation get from the field.
  873. * @return if return {@code true} that the value is a valid email address, otherwise no.
  874. */
  875. private boolean verify(Object value, Email annotation) {
  876. if (Objects.isNull(annotation)) {
  877. return true;
  878. }
  879. setVerifyMessage(annotation.message());
  880. if (Objects.isNull(value)) {
  881. return false;
  882. }
  883. if (!isCharSequence(value)) {
  884. setIllegalArgMessage("The @Email only supports CharSequence.");
  885. return false;
  886. }
  887. //@Email need trim
  888. final String val = ((CharSequence) value).toString().trim();
  889. if (val.isEmpty()) {
  890. return false;
  891. }
  892. // cannot split email string at @ as it can be a part of quoted local part of email.
  893. // so we need to split at a position of last @ present in the string:
  894. int splitPosition = val.lastIndexOf('@');
  895. if (splitPosition < 0) {
  896. return false;
  897. }
  898. String localPart = val.substring(0, splitPosition);
  899. String domainPart = val.substring(splitPosition + 1);
  900. // verify the local part
  901. if (localPart.length() > MAX_LOCAL_PART_LENGTH) {
  902. return false;
  903. }
  904. if (!LOCAL_PART_PATTERN.matcher(localPart).matches()) {
  905. return false;
  906. }
  907. // verify the domain part
  908. // if we have a trailing dot the domain part we have an invalid email address.
  909. // the regular expression match would take care of this, but IDN.toASCII drops the trailing '.'
  910. if (domainPart.endsWith(".")) {
  911. return false;
  912. }
  913. Matcher matcher = EMAIL_DOMAIN_PATTERN.matcher(domainPart);
  914. if (!matcher.matches()) {
  915. return false;
  916. }
  917. String ascii;
  918. try {
  919. ascii = IDN.toASCII(domainPart);
  920. } catch (IllegalArgumentException e) {
  921. setIllegalArgMessage("Failed to convert domain string to ASCII code. Exception: %s", e.getMessage());
  922. return false;
  923. }
  924. if (MAX_DOMAIN_PART_LENGTH < ascii.length()) {
  925. return false;
  926. }
  927. final Pattern.Flag[] flags = annotation.flags();
  928. final String regexp = annotation.regexp();
  929. int intFlag = 0;
  930. for (Pattern.Flag flag : flags) {
  931. intFlag = intFlag | flag.getValue();
  932. }
  933. java.util.regex.Pattern pattern = null;
  934. // we only apply the regexp if there is one to apply
  935. if (!".*".equals(regexp) || flags.length > 0) {
  936. try {
  937. pattern = java.util.regex.Pattern.compile(regexp, intFlag);
  938. } catch (PatternSyntaxException e) {
  939. setIllegalArgMessage("Failed to Compile the regexp and flag for @Email. Exception: %s", e.getMessage());
  940. return false;
  941. }
  942. }
  943. if (Objects.isNull(pattern)) {
  944. setIllegalArgMessage("The regexp for @Email is Invalid regular expression.");
  945. return false;
  946. }
  947. return pattern.matcher(val).matches();
  948. }
  949. /**
  950. * verify whether the value is matches the pattern.
  951. *
  952. * @param value any object value, but only work for {@link CharSequence}
  953. * @param annotation the {@link Pattern} annotation get from the field.
  954. * @return if return {@code true} that the value is mathches the pattern, otherwise no.
  955. */
  956. private boolean verify(Object value, Pattern annotation) {
  957. if (Objects.isNull(annotation)) {
  958. return true;
  959. }
  960. setVerifyMessage(annotation.message());
  961. if (Objects.isNull(value)) {
  962. return false;
  963. }
  964. if (!isCharSequence(value)) {
  965. setIllegalArgMessage("The @Pattern only supports CharSequence.");
  966. return false;
  967. }
  968. //@Pattern no trim
  969. final String val = ((CharSequence) value).toString();
  970. if (val.isEmpty()) {
  971. return false;
  972. }
  973. final Pattern.Flag[] flags = annotation.flags();
  974. final String regexp = annotation.regexp();
  975. int intFlag = 0;
  976. for (Pattern.Flag flag : flags) {
  977. intFlag = intFlag | flag.getValue();
  978. }
  979. java.util.regex.Pattern pattern;
  980. try {
  981. pattern = java.util.regex.Pattern.compile(regexp, intFlag);
  982. } catch (PatternSyntaxException e) {
  983. setIllegalArgMessage("Failed to Compile the regexp and flag for @Pattern. Exception: %s", e.getMessage());
  984. return false;
  985. }
  986. if (Objects.isNull(pattern)) {
  987. setIllegalArgMessage("The regexp for @Pattern is Invalid regular expression.");
  988. return false;
  989. }
  990. return pattern.matcher(val).matches();
  991. }
  992. /**
  993. * verify whether the value less then or equal the maximum value.
  994. *
  995. * @param value any object value, but only work for {@link CharSequence} and {@link Number}
  996. * @param annotation the {@link Max} annotation get from the field.
  997. * @return if return {@code ture} that the value is less than or equal the maximum, otherwise no.
  998. */
  999. private boolean verify(Object value, Max annotation) {
  1000. if (Objects.isNull(annotation)) {
  1001. return true;
  1002. }
  1003. setVerifyMessage(annotation.message());
  1004. if (Objects.isNull(value)) {
  1005. return false;
  1006. }
  1007. final boolean isNumber = isNumber(value);
  1008. final boolean isCharSequence = isCharSequence(value);
  1009. if (!isCharSequence && !isNumber) {
  1010. setIllegalArgMessage("The @Max only supports Number & CharSequence.");
  1011. return false;
  1012. }
  1013. final long max = annotation.value();
  1014. final int compare;
  1015. if (isNumber) {
  1016. compare = numberComparator((Number) value, max, GREATER_THAN);
  1017. } else {
  1018. String v = ((CharSequence) value).toString().trim();
  1019. if (v.isEmpty()) {
  1020. return false;
  1021. }
  1022. BigDecimal val = newBigDecimal(v);
  1023. if (Objects.isNull(val)) {
  1024. return false;
  1025. }
  1026. compare = val.compareTo(BigDecimal.valueOf(max));
  1027. }
  1028. //compare <= 0
  1029. return compare <= 0;
  1030. }
  1031. /**
  1032. * verify whether the value greater then or equal the minimum value.
  1033. *
  1034. * @param value any object value, but only work for {@link CharSequence} and {@link Number}
  1035. * @param annotation the {@link Max} annotation get from the field.
  1036. * @return if return {@code ture} that the value is less than or equal the minimum, otherwise no.
  1037. */
  1038. private boolean verify(Object value, Min annotation) {
  1039. if (Objects.isNull(annotation)) {
  1040. return true;
  1041. }
  1042. setVerifyMessage(annotation.message());
  1043. if (Objects.isNull(value)) {
  1044. return false;
  1045. }
  1046. final boolean isNumber = isNumber(value);
  1047. final boolean isCharSequence = isCharSequence(value);
  1048. if (!isCharSequence && !isNumber) {
  1049. setIllegalArgMessage("The @Min only supports Number & CharSequence.");
  1050. return false;
  1051. }
  1052. final long min = annotation.value();
  1053. final int compare;
  1054. if (isNumber) {
  1055. compare = numberComparator((Number) value, min, LESS_THAN);
  1056. } else {
  1057. String v = ((CharSequence) value).toString().trim();
  1058. if (v.isEmpty()) {
  1059. return false;
  1060. }
  1061. BigDecimal val = newBigDecimal(v);
  1062. if (Objects.isNull(val)) {
  1063. return false;
  1064. }
  1065. compare = val.compareTo(BigDecimal.valueOf(min));
  1066. }
  1067. //compare >= 0
  1068. return compare >= 0;
  1069. }
  1070. /**
  1071. * verify whether the value
  1072. *
  1073. * @param value any object value, but only work for {@link Number}
  1074. * @param annotation the {@link Negative} annotation get from the field.
  1075. * @return
  1076. */
  1077. private boolean verify(Object value, Negative annotation) {
  1078. if (Objects.isNull(annotation)) {
  1079. return true;
  1080. }
  1081. setVerifyMessage(annotation.message());
  1082. if (Objects.isNull(value)) {
  1083. return false;
  1084. }
  1085. if (!isNumber(value)) {
  1086. setIllegalArgMessage("The @Negative only supports Number.");
  1087. return false;
  1088. }
  1089. return signum((Number) value, GREATER_THAN) < 0;
  1090. }
  1091. /**
  1092. * @param value any object value, but only work for {@link Number}
  1093. * @param annotation the {@link NegativeOrZero} annotation get from the field.
  1094. * @return
  1095. */
  1096. private boolean verify(Object value, NegativeOrZero annotation) {
  1097. if (Objects.isNull(annotation)) {
  1098. return true;
  1099. }
  1100. setVerifyMessage(annotation.message());
  1101. if (Objects.isNull(value)) {
  1102. return false;
  1103. }
  1104. if (!isNumber(value)) {
  1105. setIllegalArgMessage("The @NegativeOrZero only supports Number.");
  1106. return false;
  1107. }
  1108. return signum((Number) value, GREATER_THAN) <= 0;
  1109. }
  1110. /**
  1111. * @param value any object value, but only work for {@link Number}
  1112. * @param annotation the {@link Positive} annotation get from the field.
  1113. * @return
  1114. */
  1115. private boolean verify(Object value, Positive annotation) {
  1116. if (Objects.isNull(annotation)) {
  1117. return true;
  1118. }
  1119. setVerifyMessage(annotation.message());
  1120. if (Objects.isNull(value)) {
  1121. return false;
  1122. }
  1123. if (!isNumber(value)) {
  1124. setIllegalArgMessage("The @Positive only supports Number.");
  1125. return false;
  1126. }
  1127. return signum((Number) value, LESS_THAN) > 0;
  1128. }
  1129. /**
  1130. * @param value any object value, but only work for {@link Number}
  1131. * @param annotation the {@link PositiveOrZero} annotation get from the field.
  1132. * @return
  1133. */
  1134. private boolean verify(Object value, PositiveOrZero annotation) {
  1135. if (Objects.isNull(annotation)) {
  1136. return true;
  1137. }
  1138. setVerifyMessage(annotation.message());
  1139. if (Objects.isNull(value)) {
  1140. return false;
  1141. }
  1142. if (!isNumber(value)) {
  1143. setIllegalArgMessage("The @PositiveOrZero only supports Number.");
  1144. return false;
  1145. }
  1146. return signum((Number) value, LESS_THAN) >= 0;
  1147. }
  1148. /**
  1149. * compare the TemporalAccessor value with now , verify whether the value is before or after now.
  1150. *
  1151. * @param value any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
  1152. * @param isTemporalAccessor whether the value is instance of {@link TemporalAccessor}
  1153. * @param isDate whether the value is instance of {@link Date}
  1154. * @return {@code -1} the value is before now, {@code 0} is now, {@code 1} after now. otherwise {@code -2} not support the type.
  1155. */
  1156. private int dateComparator(Object value, boolean isTemporalAccessor, boolean isDate) {
  1157. Clock clock = systemDefaultClock();
  1158. int compare;
  1159. if (isTemporalAccessor) {
  1160. if (value instanceof Instant) {
  1161. compare = ((Instant) value).compareTo(Instant.now(clock));
  1162. } else if (value instanceof LocalDateTime) {
  1163. compare = ((LocalDateTime) value).compareTo(LocalDateTime.now(clock));
  1164. } else if (value instanceof LocalDate) {
  1165. compare = ((LocalDate) value).compareTo(LocalDate.now(clock));
  1166. } else if (value instanceof LocalTime) {
  1167. compare = ((LocalTime) value).compareTo(LocalTime.now(clock));
  1168. } else if (value instanceof MonthDay) {
  1169. compare = ((MonthDay) value).compareTo(MonthDay.now(clock));
  1170. } else if (value instanceof HijrahDate) {
  1171. compare = ((HijrahDate) value).compareTo(HijrahDate.now(clock));
  1172. } else if (value instanceof JapaneseDate) {
  1173. compare = ((JapaneseDate) value).compareTo(JapaneseDate.now(clock));
  1174. } else if (value instanceof MinguoDate) {
  1175. compare = ((MinguoDate) value).compareTo(MinguoDate.now(clock));
  1176. } else if (value instanceof OffsetDateTime) {
  1177. compare = ((OffsetDateTime) value).compareTo(OffsetDateTime.now(clock));
  1178. } else if (value instanceof OffsetTime) {
  1179. compare = ((OffsetTime) value).compareTo(OffsetTime.now(clock));
  1180. } else if (value instanceof ThaiBuddhistDate) {
  1181. compare = ((ThaiBuddhistDate) value).compareTo(ThaiBuddhistDate.now(clock));
  1182. } else if (value instanceof Year) {
  1183. compare = ((Year) value).compareTo(Year.now(clock));
  1184. } else if (value instanceof YearMonth) {
  1185. compare = ((YearMonth) value).compareTo(YearMonth.now(clock));
  1186. } else if (value instanceof ZonedDateTime) {
  1187. compare = ((ZonedDateTime) value).compareTo(ZonedDateTime.now(clock));
  1188. } else {
  1189. setIllegalArgMessage("The '%s' is not a supported TemporalAccessor class temporarily.", value.getClass().getCanonicalName());
  1190. compare = Integer.MAX_VALUE;
  1191. }
  1192. } else if (isDate) {
  1193. Date val = (Date) value;
  1194. compare = val.toInstant().compareTo(Instant.now(clock));
  1195. } else {
  1196. Calendar val = (Calendar) value;
  1197. compare = val.toInstant().compareTo(Instant.now(clock));
  1198. }
  1199. return compare;
  1200. }
  1201. /**
  1202. * verify whether the value is a future time.
  1203. *
  1204. * @param value any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
  1205. * @param annotation the {@link Future} annotation get from the field.
  1206. * @return if return {@code true} that the value is a future time, otherwise no.
  1207. */
  1208. private boolean verify(Object value, Future annotation) {
  1209. if (Objects.isNull(annotation)) {
  1210. return true;
  1211. }
  1212. setVerifyMessage(annotation.message());
  1213. if (Objects.isNull(value)) {
  1214. return false;
  1215. }
  1216. final boolean isTemporalAccessor = isTemporalAccessor(value);
  1217. final boolean isDate = isDate(value);
  1218. final boolean isCalendar = isCalendar(value);
  1219. if (!isTemporalAccessor && !isDate && !isCalendar) {
  1220. setIllegalArgMessage("The @Future only supports TemporalAccessor & Calendar & Date.");
  1221. return false;
  1222. }
  1223. final int compare = dateComparator(value, isTemporalAccessor, isDate);
  1224. if (Integer.MAX_VALUE == compare) {
  1225. return false;
  1226. }
  1227. //compare > 0
  1228. return compare > 0;
  1229. }
  1230. /**
  1231. * verify whether the value is a future time or present.
  1232. *
  1233. * @param value any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
  1234. * @param annotation the {@link FutureOrPresent} annotation get from the field.
  1235. * @return if return {@code true} that the value is a future time or present, otherwise no.
  1236. */
  1237. private boolean verify(Object value, FutureOrPresent annotation) {
  1238. if (Objects.isNull(annotation)) {
  1239. return true;
  1240. }
  1241. setVerifyMessage(annotation.message());
  1242. if (Objects.isNull(value)) {
  1243. return false;
  1244. }
  1245. final boolean isTemporalAccessor = isTemporalAccessor(value);
  1246. final boolean isDate = isDate(value);
  1247. final boolean isCalendar = isCalendar(value);
  1248. if (!isTemporalAccessor && !isDate && !isCalendar) {
  1249. setIllegalArgMessage("The @FutureOrPresent only supports TemporalAccessor & Calendar & Date.");
  1250. return false;
  1251. }
  1252. final int compare = dateComparator(value, isTemporalAccessor, isDate);
  1253. if (Integer.MAX_VALUE == compare) {
  1254. return false;
  1255. }
  1256. //compare >= 0
  1257. return compare >= 0;
  1258. }
  1259. /**
  1260. * verify whether the value is a past time.
  1261. *
  1262. * @param value any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
  1263. * @param annotation the {@link Past} annotation get from the field.
  1264. * @return if return {@code true} that the value is a past time, otherwise no.
  1265. */
  1266. private boolean verify(Object value, Past annotation) {
  1267. if (Objects.isNull(annotation)) {
  1268. return true;
  1269. }
  1270. setVerifyMessage(annotation.message());
  1271. if (Objects.isNull(value)) {
  1272. return false;
  1273. }
  1274. final boolean isTemporalAccessor = isTemporalAccessor(value);
  1275. final boolean isDate = isDate(value);
  1276. final boolean isCalendar = isCalendar(value);
  1277. if (!isTemporalAccessor && !isDate && !isCalendar) {
  1278. setIllegalArgMessage("The @Past only supports TemporalAccessor & Calendar & Date.");
  1279. return false;
  1280. }
  1281. final int compare = dateComparator(value, isTemporalAccessor, isDate);
  1282. if (Integer.MAX_VALUE == compare) {
  1283. return false;
  1284. }
  1285. //compare < 0
  1286. return compare < 0;
  1287. }
  1288. /**
  1289. * verify whether the value is a future time or present.
  1290. *
  1291. * @param value any object value, but only work for {@link TemporalAccessor}, {@link Date} and {@link Calendar}
  1292. * @param annotation the {@link Future} annotation get from the field.
  1293. * @return if return {@code true} that the value is a past time or present, otherwise no.
  1294. */
  1295. private boolean verify(Object value, PastOrPresent annotation) {
  1296. if (Objects.isNull(annotation)) {
  1297. return true;
  1298. }
  1299. setVerifyMessage(annotation.message());
  1300. if (Objects.isNull(value)) {
  1301. return false;
  1302. }
  1303. final boolean isTemporalAccessor = isTemporalAccessor(value);
  1304. final boolean isDate = isDate(value);
  1305. final boolean isCalendar = isCalendar(value);
  1306. if (!isTemporalAccessor && !isDate && !isCalendar) {
  1307. setIllegalArgMessage("The @PastOrPresent only supports TemporalAccessor & Calendar & Date.");
  1308. return false;
  1309. }
  1310. final int compare = dateComparator(value, isTemporalAccessor, isDate);
  1311. if (Integer.MAX_VALUE == compare) {
  1312. return false;
  1313. }
  1314. //compare <= 0
  1315. return compare <= 0;
  1316. }
  1317. /**
  1318. * verify the fields of the object base on the annotation present on it.
  1319. * if it is not valid that will throw a {@link VerifyException}.
  1320. * or a {@link IllegalArgumentException} when argument is illegal.
  1321. * <p>
  1322. * {@code object} any object value. with some validation annotation in {@code javax.validation.constraints.*}
  1323. * {@code fields} the list of fields to verify. if no, that will verify all fields in the object.
  1324. */
  1325. private void action() {
  1326. isNull(object, "The object to verify must not be null.");
  1327. Field[] verifyFields = filterFields(object, fields);
  1328. for (Field verifyField : verifyFields) {
  1329. //filter the validation annotation
  1330. Annotation[] verifyAnnos = filterAnnotations(verifyField);
  1331. if (isEmpty(verifyAnnos)) continue;
  1332. //get value.
  1333. Object value = obtainFieldValue(object, verifyField);
  1334. for (Annotation verifyAnno : verifyAnnos) {
  1335. //get method
  1336. Method method = lookupMethod(this.getClass(), VerifyMethodName, verifyAnno.annotationType());
  1337. // result.
  1338. boolean invoke = invokeMethod(this, method, value, verifyAnno);
  1339. if (invoke) continue;
  1340. if (logWrite) {
  1341. write(verifyField, value, method, verifyAnno);
  1342. }
  1343. if (1 == code) {
  1344. throw newVerifyException(message);
  1345. } else {
  1346. throw newIllegalArgException(message);
  1347. }
  1348. }
  1349. }
  1350. }
  1351. }
  1352. /**
  1353. * get field by the names
  1354. *
  1355. * @param object the target object
  1356. * @param fields the names of field
  1357. * @return list of field
  1358. */
  1359. private static Field[] filterFields(final Object object, final String... fields) {
  1360. Class<?> objectClass = object.getClass();
  1361. // get fields, if has specify fields, otherwise get all fields on the object.
  1362. if (isEmpty(fields)) {
  1363. return objectClass.getDeclaredFields();
  1364. }
  1365. List<Field> verifyFields = new ArrayList<>();
  1366. for (String field : fields) {
  1367. Field verifyField;
  1368. try {
  1369. verifyField = objectClass.getDeclaredField(field);
  1370. } catch (NoSuchFieldException e) {
  1371. throw newIllegalArgException("No such field '%s' in : %s.", field, objectClass.getCanonicalName());
  1372. }
  1373. verifyFields.add(verifyField);
  1374. }
  1375. //if vaild fields is empty
  1376. if (verifyFields.isEmpty()) {
  1377. throw newIllegalArgException("The specified field names did not resolve any available fields to verify.");
  1378. }
  1379. return verifyFields.toArray(new Field[verifyFields.size()]);
  1380. }
  1381. /**
  1382. * get all validation annotation on the field
  1383. *
  1384. * @param field the target field
  1385. * @return list of validation annotation
  1386. */
  1387. private static Annotation[] filterAnnotations(final Field field) {
  1388. if (!field.isAccessible()) {
  1389. field.setAccessible(true);
  1390. }
  1391. if (FieldAnnotationCache.keySet().contains(field)) {
  1392. return FieldAnnotationCache.get(field);
  1393. }
  1394. //get all annotation of the field
  1395. Annotation[] annotations = field.getDeclaredAnnotations();
  1396. //filter the validation annotation
  1397. if (notEmpty(annotations)) {
  1398. List<Annotation> verifyAnnos = Arrays.stream(annotations).filter(anno -> ClassMethodCache.keySet().contains(anno.annotationType())).collect(Collectors.toList());
  1399. annotations = verifyAnnos.toArray(new Annotation[verifyAnnos.size()]);
  1400. }
  1401. FieldAnnotationCache.put(field, annotations);
  1402. log.info("Get Declared annotation on field '{}', cache size: {}", field.getName(), FieldAnnotationCache.size());
  1403. return annotations;
  1404. }
  1405. /**
  1406. * get value of the field on the object
  1407. *
  1408. * @param object the target object
  1409. * @param field the field
  1410. * @return the value
  1411. */
  1412. private static Object obtainFieldValue(final Object object, final Field field) {
  1413. final Object value;
  1414. try {
  1415. value = field.get(object);
  1416. } catch (IllegalAccessException e) {
  1417. throw newIllegalArgException("Illegal Access '%s' in %s.", field.getName(), object.getClass().getCanonicalName());
  1418. }
  1419. return value;
  1420. }
  1421. private static Method lookupMethod(final Class<?> target, final String methodName, final Class<?> annotationType) {
  1422. if (ClassMethodCache.keySet().contains(annotationType)) {
  1423. return ClassMethodCache.get(annotationType);
  1424. }
  1425. final Method method;
  1426. try {
  1427. method = target.getDeclaredMethod(methodName, Object.class, annotationType);
  1428. } catch (NoSuchMethodException e) {
  1429. throw newIllegalArgException("No such method [%s(Object,%s)] in %s.", methodName, annotationType, target.getName());
  1430. }
  1431. if (!method.isAccessible()) {
  1432. method.setAccessible(true);
  1433. }
  1434. ClassMethodCache.put(annotationType, method);
  1435. return method;
  1436. }
  1437. /**
  1438. * look up the {@link MethodHandle} for the target verify.
  1439. *
  1440. * @param lookup {@link MethodHandles#lookup()}
  1441. * @param methodName the name of method
  1442. * @param annotationType the return type and args type
  1443. * @return MethodHandle
  1444. */
  1445. private static MethodHandle lookupMethod(final MethodHandles.Lookup lookup, final String methodName, final Class<?> annotationType) {
  1446. if (ClassMethodHandleCache.keySet().contains(annotationType)) {
  1447. return ClassMethodHandleCache.get(annotationType);
  1448. }
  1449. final Class<?> lookupClass = lookup.lookupClass();
  1450. final MethodHandle methodHandle;
  1451. final MethodType methodType = MethodType.methodType(boolean.class, Object.class, annotationType);
  1452. try {
  1453. methodHandle = lookup.findVirtual(lookupClass, methodName, methodType);
  1454. } catch (NoSuchMethodException | IllegalAccessException e) {
  1455. throw newIllegalArgException("No such method [%s(%s)] in %s.", methodName, methodType, lookupClass.getName());
  1456. }
  1457. ClassMethodHandleCache.put(annotationType, methodHandle);
  1458. return methodHandle;
  1459. }
  1460. /**
  1461. * invoke the target methodHandle to verify
  1462. *
  1463. * @param target the target object
  1464. * @param methodHandle the verify methodHandle
  1465. * @param args the list of args
  1466. * @return boolean
  1467. */
  1468. private static boolean invokeMethod(final Object target, final MethodHandle methodHandle, final Object... args) {
  1469. final boolean invoke;
  1470. try {
  1471. invoke = (boolean) methodHandle.bindTo(target).invokeWithArguments(args);
  1472. } catch (Throwable throwable) {
  1473. throw newIllegalArgException("Invoke the target method [%s] failed.", methodHandle);
  1474. }
  1475. return invoke;
  1476. }
  1477. /**
  1478. * invoke the target method to verify.
  1479. *
  1480. * @param target the target object
  1481. * @param method the target method
  1482. * @param value the value
  1483. * @param annotation the annotation
  1484. * @return boolean
  1485. */
  1486. private static boolean invokeMethod(final Object target, final Method method, final Object value, final Annotation annotation) {
  1487. final boolean invoke;
  1488. try {
  1489. invoke = (boolean) method.invoke(target, value, annotation);
  1490. } catch (IllegalAccessException | InvocationTargetException e) {
  1491. throw newIllegalArgException("Invoke the target method [%s(Object,%s)] failed.", method.getName(), annotation.annotationType().getName());
  1492. }
  1493. return invoke;
  1494. }
  1495. /**
  1496. * the format of the {@code info} level log.
  1497. *
  1498. * @param verifyField the field to valid.
  1499. * @param value the value of the field.
  1500. * @param method the method to action
  1501. * @param verifyAnno the validation annotation of the field.
  1502. */
  1503. private static void write(Field verifyField, Object value, Method method, Annotation verifyAnno) {
  1504. log.warn("###| FIELD : {}", verifyField);
  1505. log.warn("###| FIELD_VALUE : {}", value);
  1506. log.warn("###| METHOD : (false) {}(Object,{})", method.getName(), verifyAnno.annotationType().getName());
  1507. }
  1508. /**
  1509. * create a {@link IllegalArgumentException} with the message.
  1510. *
  1511. * @param message the message of exception.
  1512. * @return a {@link IllegalArgumentException}
  1513. */
  1514. private static IllegalArgumentException newIllegalArgException(String message, Object... values) {
  1515. return new IllegalArgumentException(format(message, values));
  1516. }
  1517. /**
  1518. * create a {@link VerifyException} with the message.
  1519. *
  1520. * @param message the message of exception.
  1521. * @return a {@link VerifyException}
  1522. */
  1523. private static VerifyException newVerifyException(String message, Object... values) {
  1524. return new VerifyException(format(message, values));
  1525. }
  1526. /**
  1527. * format the message with some values
  1528. *
  1529. * @param message the message string.
  1530. * @param values some values.
  1531. * @return the message has format if necessary
  1532. */
  1533. private static String format(String message, Object... values) {
  1534. if(message.contains("%s") && null != values && values.length > 0) {
  1535. return String.format(message, values);
  1536. }
  1537. return message;
  1538. }
  1539. /**
  1540. * verify whether the expression is {@code true}.
  1541. *
  1542. * @param object any object.
  1543. * @param message if {@code true} that throw a {@link IllegalArgumentException} with message.
  1544. */
  1545. private static void isNull(Object object, String message) {
  1546. if (null == object) {
  1547. throw newIllegalArgException(message);
  1548. }
  1549. }
  1550. private static void isTrue(boolean expression, String message) {
  1551. if (expression) {
  1552. throw newIllegalArgException(message);
  1553. }
  1554. }
  1555. /**
  1556. * to support {@link SecurityManager}
  1557. *
  1558. * @param action {@link PrivilegedAction}
  1559. * @param <T> the type of return.
  1560. * @return return the result.
  1561. */
  1562. private <T> T run(PrivilegedAction<T> action) {
  1563. return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run();
  1564. }
  1565. }