VerifyProcessor.java 66 KB

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