|
@@ -5,14 +5,13 @@ import cn.hutool.core.util.ArrayUtil;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
import javax.validation.constraints.*;
|
|
|
+import java.io.ObjectStreamException;
|
|
|
import java.lang.annotation.Annotation;
|
|
|
import java.lang.invoke.MethodHandle;
|
|
|
import java.lang.invoke.MethodHandles;
|
|
|
import java.lang.invoke.MethodType;
|
|
|
import java.lang.reflect.Array;
|
|
|
import java.lang.reflect.Field;
|
|
|
-import java.lang.reflect.InvocationTargetException;
|
|
|
-import java.lang.reflect.Method;
|
|
|
import java.math.BigDecimal;
|
|
|
import java.math.BigInteger;
|
|
|
import java.net.IDN;
|
|
@@ -38,11 +37,6 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE;
|
|
|
*/
|
|
|
@Slf4j
|
|
|
public class Verification {
|
|
|
-
|
|
|
- /**
|
|
|
- * get a singleton instance of the judgement.
|
|
|
- */
|
|
|
- public static final Verification INSTANCE = new Verification();
|
|
|
/**
|
|
|
* the name of the verify method.
|
|
|
*/
|
|
@@ -51,14 +45,6 @@ public class Verification {
|
|
|
* the class of the first agrument for the verify method.
|
|
|
*/
|
|
|
private final Class firstArgument = Object.class;
|
|
|
- /**
|
|
|
- * the code of verify message.
|
|
|
- */
|
|
|
- private final int verifyCode = 1;
|
|
|
- /**
|
|
|
- * the code of illegal argument.
|
|
|
- */
|
|
|
- private final int illegalArgCode = 2;
|
|
|
/**
|
|
|
* whether write log to file. it's {@code true} by default.
|
|
|
*/
|
|
@@ -153,6 +139,30 @@ public class Verification {
|
|
|
*/
|
|
|
private Verification() {
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * the static inner class to builder a singleton instance.
|
|
|
+ */
|
|
|
+ private static class Builder {
|
|
|
+ private static Verification INSTANCE = new Verification();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * get the singleton instance of this
|
|
|
+ */
|
|
|
+ public static Verification getInstance() {
|
|
|
+ return Builder.INSTANCE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 因为单例模式虽然能保证线程安全,但在序列化和反序列化的情况下会出现生成多个对象的情况.
|
|
|
+ * readResolve()方法,其实当JVM从内存中反序列化地"组装"一个新对象时,
|
|
|
+ * 就会自动调用这个readResolve方法来返回指定好的对象, 单例规则也就得到了保证。
|
|
|
+ * readResolve()的出现允许程序员自行控制通过反序列化得到的对象。
|
|
|
+ */
|
|
|
+ protected Object readResolve() throws ObjectStreamException {
|
|
|
+ return Builder.INSTANCE;
|
|
|
+ }
|
|
|
/**
|
|
|
* setting whether write log to file.
|
|
|
*
|
|
@@ -193,14 +203,14 @@ public class Verification {
|
|
|
* set the verify message in the ThreadLocal.(code : 1)
|
|
|
*/
|
|
|
private void setVerify(String message) {
|
|
|
- this.code.set(verifyCode);
|
|
|
+ this.code.set(1);
|
|
|
this.message.set(message);
|
|
|
}
|
|
|
/**
|
|
|
* set the illegal argument message in the ThreadLocal.(code : 2)
|
|
|
*/
|
|
|
private void setIllegalArg(String message) {
|
|
|
- this.code.set(illegalArgCode);
|
|
|
+ this.code.set(2);
|
|
|
this.message.set(message);
|
|
|
}
|
|
|
|
|
@@ -855,6 +865,10 @@ public class Verification {
|
|
|
return false;
|
|
|
}
|
|
|
int length = length(value);
|
|
|
+ if(-1 == length){
|
|
|
+ setIllegalArg("The NotEmpty is only for CharSequence & Collection & Map & Array & Iterator & Enumeration.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
// length > 0
|
|
|
return gt0(length);
|
|
|
}
|
|
@@ -914,12 +928,21 @@ public class Verification {
|
|
|
if (isNull(annotation)) {
|
|
|
return true;
|
|
|
}
|
|
|
- setVerify(annotation.message());
|
|
|
+ int min = annotation.min();
|
|
|
+ int max = annotation.max();
|
|
|
+ String message = annotation.message();
|
|
|
+ if(null != message && !message.trim().isEmpty()){
|
|
|
+ if(message.contains("{min}")){
|
|
|
+ message = message.replace("{min}", String.valueOf(min));
|
|
|
+ }
|
|
|
+ if(message.contains("{max}")){
|
|
|
+ message = message.replace("{max}", String.valueOf(max));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setVerify(message);
|
|
|
if (isNull(value)) {
|
|
|
return false;
|
|
|
}
|
|
|
- int min = annotation.min();
|
|
|
- int max = annotation.max();
|
|
|
if (lt0(min)) {
|
|
|
setIllegalArg("The min parameter of Size cannot be negative.");
|
|
|
return false;
|
|
@@ -933,6 +956,10 @@ public class Verification {
|
|
|
return false;
|
|
|
}
|
|
|
int length = length(value);
|
|
|
+ if(-1 == length){
|
|
|
+ setIllegalArg("The Size is only for CharSequence & Collection & Map & Array & Iterator & Enumeration.");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
//size >= min && size <= max
|
|
|
return length >= min && length <= max;
|
|
|
}
|
|
@@ -948,7 +975,18 @@ public class Verification {
|
|
|
if (isNull(annotation)) {
|
|
|
return true;
|
|
|
}
|
|
|
- setVerify(annotation.message());
|
|
|
+ int maxInteger = annotation.integer();
|
|
|
+ int maxFraction = annotation.fraction();
|
|
|
+ String message = annotation.message();
|
|
|
+ if(null != message && !message.trim().isEmpty()){
|
|
|
+ if(message.contains("{integer}")){
|
|
|
+ message = message.replace("integer", String.valueOf(maxInteger));
|
|
|
+ }
|
|
|
+ if(message.contains("{fraction}")){
|
|
|
+ message = message.replace("fraction", String.valueOf(maxFraction));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setVerify(message);
|
|
|
if (isNull(value)) {
|
|
|
return false;
|
|
|
}
|
|
@@ -958,8 +996,6 @@ public class Verification {
|
|
|
setIllegalArg("The Digits is only for Number & CharSequence.");
|
|
|
return false;
|
|
|
}
|
|
|
- int maxInteger = annotation.integer();
|
|
|
- int maxFraction = annotation.fraction();
|
|
|
if (lt0(maxInteger)) {
|
|
|
setIllegalArg("The length of the integer part cannot be negative.");
|
|
|
return false;
|
|
@@ -1115,7 +1151,7 @@ public class Verification {
|
|
|
try {
|
|
|
pattern = java.util.regex.Pattern.compile(regexp, intFlag);
|
|
|
} catch (PatternSyntaxException e) {
|
|
|
- setIllegalArg("The regexp for Pattern is Invalid regular expression.");
|
|
|
+ setIllegalArg("Compile the regexp and flag for Pattern fail.");
|
|
|
return false;
|
|
|
}
|
|
|
if (isNull(pattern)) {
|
|
@@ -1550,84 +1586,151 @@ public class Verification {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * verify the fields of the object base on the annotation present on it.
|
|
|
- * if it is not valid that will throw a {@link VerifyException}.
|
|
|
- * or a {@link IllegalArgumentException} when argument is illegal.
|
|
|
+ * get field by the names
|
|
|
*
|
|
|
- * @param object any object value. with some validation annotation in {@code javax.validation.constraints.*}
|
|
|
- * @param fields the list of fields to verify. if no, that will verify all fields in the object.
|
|
|
+ * @param object the target object
|
|
|
+ * @param fields the names of field
|
|
|
+ * @return list of field
|
|
|
*/
|
|
|
- public void action(final Object object, final String... fields) {
|
|
|
- isTrueIllegal(isNull(object), "The object to verify must not be null.");
|
|
|
-
|
|
|
+ private List<Field> fieldFilter(final Object object, final String... fields) {
|
|
|
Class<?> objectClass = object.getClass();
|
|
|
-
|
|
|
- List<Field> verifyFields = new ArrayList<>();
|
|
|
- boolean specifyField = gt0(length(fields));
|
|
|
-
|
|
|
// get fields, if has specify fields, otherwise get all fields on the object.
|
|
|
- if (specifyField) {
|
|
|
+ if (ArrayUtil.isNotEmpty(fields)) {
|
|
|
+ List<Field> verifyFields = new ArrayList<>();
|
|
|
for (String field : fields) {
|
|
|
+ Field verifyField = null;
|
|
|
try {
|
|
|
- Field declaredField = objectClass.getDeclaredField(field);
|
|
|
- verifyFields.add(declaredField);
|
|
|
+ //1 get the declared field on this class.
|
|
|
+ verifyField = objectClass.getDeclaredField(field);
|
|
|
} catch (NoSuchFieldException e) {
|
|
|
- throw illegalArgException("No such field [%s] in : %s.", field, objectClass.getCanonicalName());
|
|
|
}
|
|
|
+ if (isNull(verifyField)) {
|
|
|
+ try {
|
|
|
+ // 2 get from the private field on the superclass.
|
|
|
+ verifyField = objectClass.getSuperclass().getDeclaredField(field);
|
|
|
+ } catch (NoSuchFieldException e) {
|
|
|
+ throw illegalArgException("No such field [%s] in : %s.", field, objectClass.getCanonicalName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ verifyFields.add(verifyField);
|
|
|
+ }
|
|
|
+ //if vaild fields is empty
|
|
|
+ if (verifyFields.isEmpty()) {
|
|
|
+ throw illegalArgException("No field specify to verify.");
|
|
|
}
|
|
|
+ return Collections.unmodifiableList(verifyFields);
|
|
|
} else {
|
|
|
- verifyFields = Arrays.asList(objectClass.getDeclaredFields());
|
|
|
+ return Collections.unmodifiableList(Arrays.asList(objectClass.getDeclaredFields()));
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * get all validation annotation on the field
|
|
|
+ *
|
|
|
+ * @param field the target field
|
|
|
+ * @return list of validation annotation
|
|
|
+ */
|
|
|
+ private List<Annotation> annotationFilter(final Field field) {
|
|
|
+ if (!field.isAccessible()) {
|
|
|
+ field.setAccessible(true);
|
|
|
+ }
|
|
|
+ //get all annotation of the field
|
|
|
+ Annotation[] annotations = field.getDeclaredAnnotations();
|
|
|
+ if (ArrayUtil.isNotEmpty(annotations)) {
|
|
|
+ //filter the validation annotation
|
|
|
+ List<Annotation> verifyAnnos = Arrays.stream(annotations).filter(anno -> validations.contains(anno.annotationType())).collect(Collectors.toList());
|
|
|
+ if (IterUtil.isNotEmpty(verifyAnnos)) {
|
|
|
+ return Collections.unmodifiableList(verifyAnnos);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * get value of the field on the object
|
|
|
+ *
|
|
|
+ * @param object the target object
|
|
|
+ * @param field the field
|
|
|
+ * @return the value
|
|
|
+ */
|
|
|
+ private Object getFieldValue(final Object object, final Field field) {
|
|
|
+ Object value;
|
|
|
+ try {
|
|
|
+ value = field.get(object);
|
|
|
+ } catch (IllegalAccessException e) {
|
|
|
+ throw illegalArgException("Illegal Access [%s] in : %s.", field.getName(), object.getClass().getCanonicalName());
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ }
|
|
|
|
|
|
- //if vaild fields is empty
|
|
|
- if(specifyField && verifyFields.isEmpty()){
|
|
|
- throw illegalArgException("No field specify to verify.");
|
|
|
+ /**
|
|
|
+ * find the method for the verify
|
|
|
+ *
|
|
|
+ * @param lookup method handle of lookup {@link java.lang.invoke.MethodHandles.Lookup}
|
|
|
+ * @param methodName the name of the method
|
|
|
+ * @param methodType the return and the args class for the method
|
|
|
+ * @return method handle
|
|
|
+ */
|
|
|
+ private MethodHandle methodFilter(final MethodHandles.Lookup lookup, final String methodName, final MethodType methodType) {
|
|
|
+ MethodHandle methodHandle;
|
|
|
+ try {
|
|
|
+ methodHandle = lookup.findVirtual(lookup.lookupClass(), methodName, methodType);
|
|
|
+ } catch (NoSuchMethodException | IllegalAccessException e) {
|
|
|
+ throw illegalArgException("No such method [%s(%s)] in Verification.", methodName, methodType);
|
|
|
}
|
|
|
+ return methodHandle;
|
|
|
+ }
|
|
|
|
|
|
- verifyFields = Collections.unmodifiableList(verifyFields);
|
|
|
+ /**
|
|
|
+ * invoke the method bind with the target object
|
|
|
+ *
|
|
|
+ * @param methodHandle method handle{@link MethodHandle}
|
|
|
+ * @param target the target object
|
|
|
+ * @param args the list of args
|
|
|
+ * @return result
|
|
|
+ */
|
|
|
+ private boolean methodInvoke(final MethodHandle methodHandle, final Object target, final Object... args) {
|
|
|
+ boolean invoke;
|
|
|
+ try {
|
|
|
+ invoke = (boolean) methodHandle.bindTo(target).invokeWithArguments(args);
|
|
|
+ } catch (Throwable throwable) {
|
|
|
+ throw illegalArgException("Invoke the target method [%s] fail.", methodHandle);
|
|
|
+ }
|
|
|
+ return invoke;
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * verify the fields of the object base on the annotation present on it.
|
|
|
+ * if it is not valid that will throw a {@link VerifyException}.
|
|
|
+ * or a {@link IllegalArgumentException} when argument is illegal.
|
|
|
+ *
|
|
|
+ * @param object any object value. with some validation annotation in {@code javax.validation.constraints.*}
|
|
|
+ * @param fields the list of fields to verify. if no, that will verify all fields in the object.
|
|
|
+ */
|
|
|
+ public void action(final Object object, final String... fields) {
|
|
|
+ isTrueIllegal(isNull(object), "The object to verify must not be null.");
|
|
|
+ Class<?> objectClass = object.getClass();
|
|
|
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
|
|
|
+ List<Field> verifyFields = fieldFilter(object, fields);
|
|
|
for (Field verifyField : verifyFields) {
|
|
|
- if(!verifyField.isAccessible()){
|
|
|
- verifyField.setAccessible(true);
|
|
|
- }
|
|
|
- //get all annotation of the field
|
|
|
- Annotation[] annotations = verifyField.getDeclaredAnnotations();
|
|
|
- if (ArrayUtil.isEmpty(annotations)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
//filter the validation annotation
|
|
|
- List<Annotation> verifyAnnos = Arrays.stream(annotations).filter(anno -> validations.contains(anno.annotationType())).collect(Collectors.toList());
|
|
|
+ List<Annotation> verifyAnnos = annotationFilter(verifyField);
|
|
|
if (IterUtil.isEmpty(verifyAnnos)) {
|
|
|
continue;
|
|
|
}
|
|
|
- verifyAnnos = Collections.unmodifiableList(verifyAnnos);
|
|
|
//get value.
|
|
|
- Object value;
|
|
|
- try {
|
|
|
- value = verifyField.get(object);
|
|
|
- } catch (IllegalAccessException e) {
|
|
|
- throw illegalArgException("Illegal Access [%s] in : %s.", verifyField.getName(), objectClass.getCanonicalName());
|
|
|
- }
|
|
|
+ Object value = getFieldValue(object, verifyField);
|
|
|
|
|
|
for (Annotation verifyAnno : verifyAnnos) {
|
|
|
//get method
|
|
|
- Class<? extends Annotation> annotationType = verifyAnno.annotationType();
|
|
|
- boolean invoke;
|
|
|
- try {
|
|
|
- MethodHandles.Lookup lookup = MethodHandles.lookup();
|
|
|
- MethodHandle methodHandle = lookup.findVirtual(lookup.lookupClass(), verifyMethod, MethodType.methodType(boolean.class, firstArgument, annotationType));
|
|
|
- invoke = (boolean) methodHandle.bindTo(this).invokeWithArguments(value, verifyAnno);
|
|
|
- if (logWrite) {
|
|
|
- info(verifyField, value, verifyAnno, methodHandle, invoke);
|
|
|
- }
|
|
|
- } catch (NoSuchMethodException | IllegalAccessException e) {
|
|
|
- throw illegalArgException("No such method [%s(%s,%s)] in Verification.", verifyMethod, firstArgument.getCanonicalName(), annotationType.getCanonicalName());
|
|
|
- } catch (Throwable throwable) {
|
|
|
- throw illegalArgException("Invoke the target method [%s(%s,%s)] fail.", verifyMethod, firstArgument.getCanonicalName(), annotationType.getCanonicalName());
|
|
|
- }
|
|
|
+ MethodHandle methodHandle = methodFilter(lookup, verifyMethod, MethodType.methodType(boolean.class, firstArgument, verifyAnno.annotationType()));
|
|
|
+ boolean invoke = methodInvoke(methodHandle, this, value, verifyAnno);
|
|
|
// result.
|
|
|
if (!invoke) {
|
|
|
- if (illegalArgCode == code()) {
|
|
|
+ if (logWrite) {
|
|
|
+ info(verifyField, value, methodHandle);
|
|
|
+ }
|
|
|
+ if (2 == code()) {
|
|
|
throw illegalArgException(message());
|
|
|
} else {
|
|
|
throw verifyException(message());
|
|
@@ -1655,13 +1758,11 @@ public class Verification {
|
|
|
* @param method the method to action
|
|
|
* @param result the result of invoke the method.
|
|
|
*/
|
|
|
- private void info(Field verifyField, Object value, Annotation verifyAnno, MethodHandle methodHandle, boolean invoke) {
|
|
|
+ private void info(Field verifyField, Object value, MethodHandle methodHandle) {
|
|
|
log.info(logLine);
|
|
|
log.info("{}FIELD : {}", logPrefix, verifyField);
|
|
|
log.info("{}FIELD_VALUE : {}", logPrefix, value);
|
|
|
- log.info("{}@INTERFACE : {}", logPrefix, verifyAnno);
|
|
|
- log.info("{}METHOD : {}", logPrefix, methodHandle);
|
|
|
- log.info("{}RESULT : (completed) {}", logPrefix, invoke);
|
|
|
+ log.info("{}METHOD : (false) {}", logPrefix, methodHandle);
|
|
|
log.info(logLine);
|
|
|
}
|
|
|
|