Validator.java 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637
  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 Validator {
  38. /**
  39. * the name of the verify method.
  40. */
  41. private static final String validateMethodName = "validate";
  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 Validator() {
  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 = ValidateAction.class.getDeclaredMethods();
  130. List<Method> verifyMethods = Arrays.stream(declaredMethods).filter(method -> validateMethodName.equals(method.getName())).collect(Collectors.toList());
  131. isTrue(verifyMethods.isEmpty(), "No any method named " + validateMethodName + " in ValidateAction.");
  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 ValidateAction.");
  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 valid(final boolean logWrite, final Object object, final String... fields) {
  152. new ValidateAction(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 valid(final Object object, final String... fields) {
  161. new ValidateAction(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 ValidateAction {
  472. private String message;
  473. private int code;
  474. private boolean logWrite = true;
  475. private Object object;
  476. private String[] fields;
  477. private ValidateAction(boolean logWrite, Object object, String... fields) {
  478. this.logWrite = logWrite;
  479. this.object = object;
  480. this.fields = fields;
  481. }
  482. private ValidateAction(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 setValidateMessage(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 validate(Object value, NotNull annotation) {
  556. if (Objects.isNull(annotation)) {
  557. return true;
  558. }
  559. setValidateMessage(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 validate(Object value, Null annotation) {
  570. if (Objects.isNull(annotation)) {
  571. return true;
  572. }
  573. setValidateMessage(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 validate(Object value, AssertTrue annotation) {
  584. if (Objects.isNull(annotation)) {
  585. return true;
  586. }
  587. setValidateMessage(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 validate(Object value, AssertFalse annotation) {
  606. if (Objects.isNull(annotation)) {
  607. return true;
  608. }
  609. setValidateMessage(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 validate(Object value, DecimalMax annotation) {
  628. if (Objects.isNull(annotation)) {
  629. return true;
  630. }
  631. setValidateMessage(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 validate(Object value, DecimalMin annotation) {
  676. if (Objects.isNull(annotation)) {
  677. return true;
  678. }
  679. setValidateMessage(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 validate(Object value, NotEmpty annotation) {
  724. if (Objects.isNull(annotation)) {
  725. return true;
  726. }
  727. setValidateMessage(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 validate(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. setValidateMessage(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 validate(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. setValidateMessage(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 validate(Object value, NotBlank annotation) {
  855. if (Objects.isNull(annotation)) {
  856. return true;
  857. }
  858. setValidateMessage(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 validate(Object value, Email annotation) {
  876. if (Objects.isNull(annotation)) {
  877. return true;
  878. }
  879. setValidateMessage(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 validate(Object value, Pattern annotation) {
  957. if (Objects.isNull(annotation)) {
  958. return true;
  959. }
  960. setValidateMessage(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 validate(Object value, Max annotation) {
  1000. if (Objects.isNull(annotation)) {
  1001. return true;
  1002. }
  1003. setValidateMessage(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 validate(Object value, Min annotation) {
  1039. if (Objects.isNull(annotation)) {
  1040. return true;
  1041. }
  1042. setValidateMessage(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 validate(Object value, Negative annotation) {
  1078. if (Objects.isNull(annotation)) {
  1079. return true;
  1080. }
  1081. setValidateMessage(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 validate(Object value, NegativeOrZero annotation) {
  1097. if (Objects.isNull(annotation)) {
  1098. return true;
  1099. }
  1100. setValidateMessage(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 validate(Object value, Positive annotation) {
  1116. if (Objects.isNull(annotation)) {
  1117. return true;
  1118. }
  1119. setValidateMessage(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 validate(Object value, PositiveOrZero annotation) {
  1135. if (Objects.isNull(annotation)) {
  1136. return true;
  1137. }
  1138. setValidateMessage(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 validate(Object value, Future annotation) {
  1209. if (Objects.isNull(annotation)) {
  1210. return true;
  1211. }
  1212. setValidateMessage(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 validate(Object value, FutureOrPresent annotation) {
  1238. if (Objects.isNull(annotation)) {
  1239. return true;
  1240. }
  1241. setValidateMessage(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 validate(Object value, Past annotation) {
  1267. if (Objects.isNull(annotation)) {
  1268. return true;
  1269. }
  1270. setValidateMessage(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 validate(Object value, PastOrPresent annotation) {
  1296. if (Objects.isNull(annotation)) {
  1297. return true;
  1298. }
  1299. setValidateMessage(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 ValidateException}.
  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 validate must not be null.");
  1327. Field[] validateFields = filterFields(object, fields);
  1328. for (Field validateField : validateFields) {
  1329. //filter the validation annotation
  1330. Annotation[] validateAnnotations = filterAnnotations(validateField);
  1331. if (isEmpty(validateAnnotations)) continue;
  1332. //get value.
  1333. Object value = obtainFieldValue(object, validateField);
  1334. for (Annotation validateAnnotation : validateAnnotations) {
  1335. //get method
  1336. Method method = lookupMethod(this.getClass(), validateMethodName, validateAnnotation.annotationType());
  1337. // result.
  1338. boolean invoke = invokeMethod(this, method, value, validateAnnotation);
  1339. if (invoke) continue;
  1340. if (logWrite) {
  1341. write(validateField, value, method, validateAnnotation);
  1342. }
  1343. if (1 == code) {
  1344. throw newValidateException(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> validateFields = new ArrayList<>();
  1366. for (String field : fields) {
  1367. Field validateField;
  1368. try {
  1369. validateField = objectClass.getDeclaredField(field);
  1370. } catch (NoSuchFieldException e) {
  1371. throw newIllegalArgException("No such field '%s' in : %s.", field, objectClass.getCanonicalName());
  1372. }
  1373. validateFields.add(validateField);
  1374. }
  1375. //if vaild fields is empty
  1376. if (validateFields.isEmpty()) {
  1377. throw newIllegalArgException("The specified field names did not resolve any available fields to verify.");
  1378. }
  1379. return validateFields.toArray(new Field[validateFields.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> validateAnnotations = Arrays.stream(annotations).filter(annotation -> ClassMethodCache.keySet().contains(annotation.annotationType())).collect(Collectors.toList());
  1399. annotations = validateAnnotations.toArray(new Annotation[validateAnnotations.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 validateField 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 validateField, Object value, Method method, Annotation verifyAnno) {
  1504. log.warn("###| FIELD : {}", validateField);
  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 ValidateException} with the message.
  1519. *
  1520. * @param message the message of exception.
  1521. * @return a {@link ValidateException}
  1522. */
  1523. private static ValidateException newValidateException(String message, Object... values) {
  1524. return new ValidateException(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. isTrue(Objects.isNull(object), message);
  1547. }
  1548. private static void isTrue(boolean expression, String message) {
  1549. if (expression) {
  1550. throw newIllegalArgException(message);
  1551. }
  1552. }
  1553. /**
  1554. * to support {@link SecurityManager}
  1555. *
  1556. * @param action {@link PrivilegedAction}
  1557. * @param <T> the type of return.
  1558. * @return return the result.
  1559. */
  1560. private <T> T run(PrivilegedAction<T> action) {
  1561. return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run();
  1562. }
  1563. }