importcn.hutool.core.collection.CollUtil;importcn.hutool.core.date.DateUtil;importcn.hutool.core.date.TimeInterval;importcn.hutool.core.lang.Console;importcn.hutool.core.map.MapUtil;importcn.hutool.core.util.ObjectUtil;importcn.hutool.core.util.StrUtil;importcn.hutool.json.JSONUtil;importlombok.Data;importlombok.SneakyThrows;importlombok.experimental.Accessors;importorg.junit.jupiter.api.Test;importorg.springframework.util.Assert;importjava.io.Serializable;importjava.lang.invoke.SerializedLambda;importjava.lang.reflect.*;importjava.util.*;importjava.util.function.Function;/**
* @author wang.jh
* @since 2022/2/8
*/publicclassBeanUtil{publicstatic<T>Builder<T>null2Default(T t){returnnewBuilder<>(t,Builder.Full.NULL);}publicstatic<T>Builder<T>empty2Default(T t){returnnewBuilder<>(t,Builder.Full.EMPTY);}publicstatic<T>Builder<T>blank2Default(T t){returnnewBuilder<>(t,Builder.Full.BLANK);}@FunctionalInterfacepublicinterfaceMyFun<T,R>extendsFunction<T,R>,Serializable{}publicstaticclassBuilder<T>{privatefinalT target;privatefinalFull full;privateboolean strict =true;publicBuilder(T target,Full full){this.target = target;this.full = full;}privatebooleancheckGenerics(Object value,Type[] types,String fieldName){// 无泛型 or ( 泛型数量为1 and 泛型为Object类型 )if(types ==null|| types.length ==0||(types.length ==1&& value.getClass().getTypeName().equals(Object.class.getTypeName()))){returntrue;}if(value instanceofIterable){Iterable<?> iterable =(Iterable<?>) value;Class<?> typeClass =(Class<?>) types[0];for(Object item : iterable){Assert.isTrue(typeClass.isAssignableFrom(item.getClass()),StrUtil.format("field {} the generic is <{}>, but there is a value of type {}", fieldName, typeClass.getTypeName(), item.getClass().getTypeName()));}}elseif(value instanceofIterator){Class<?> typeClass =(Class<?>) types[0];Iterator<?> iterator =(Iterator<?>) value;while(iterator.hasNext()){Object item = iterator.next();Assert.isTrue(typeClass.isAssignableFrom(item.getClass()),StrUtil.format("field {} the generic is <{}>, but there is a value of type {}", fieldName, typeClass.getTypeName(), item.getClass().getTypeName()));}}elseif(value instanceofMap){Map<?,?> map =(Map<?,?>) value;if(map.size()==0){returntrue;}if(!types[0].getTypeName().equals(Object.class.getTypeName())){Set<?> keySet = map.keySet();Class<?> keyType =(Class<?>) types[0];Iterator<?> iterator = keySet.iterator();while(iterator.hasNext()){Object next = iterator.next();Assert.isTrue(keyType.isAssignableFrom(next.getClass()),StrUtil.format("field {} the generic 'key' type is <{}>, but there is a value of type {}", fieldName, keyType.getTypeName(), next.getClass().getTypeName()));}}if(!types[1].getTypeName().equals(Object.class.getTypeName())){Collection<?> values = map.values();Class<?> valueType =(Class<?>) types[1];Iterator<?> iterator = values.iterator();while(iterator.hasNext()){Object next = iterator.next();Assert.isTrue(valueType.isAssignableFrom(next.getClass()),StrUtil.format("field {} the generic 'value' type is <{}>, but there is a value of type {}", fieldName, valueType.getTypeName(), next.getClass().getTypeName()));}}returntrue;}returntrue;}publicTbuild(){return target;}publicBuilder<T>cancelStrictMode(){this.strict =false;returnthis;}privatebooleanwhetherModify(Object currentVal,Full full){if(currentVal ==null){returntrue;}switch(full){case NULL:returnObjectUtil.isNull(currentVal);case EMPTY:returnObjectUtil.isEmpty(currentVal)||cn.hutool.core.bean.BeanUtil.isEmpty(currentVal);case BLANK:returnObjectUtil.isEmpty(currentVal instanceofCharSequence?StrUtil.trim((CharSequence) currentVal): currentVal);default:returnfalse;}}@SneakyThrowspublicBuilder<T>assign(MyFun<T,?> k,Object v){// 读取lambda信息Method method = k.getClass().getDeclaredMethod("writeReplace");this.setAccessible(method);SerializedLambda serializedLambda =(SerializedLambda) method.invoke(k);// 修改source Class对象Class<?> beanClass = target.getClass();// 读取字段getter方法 -- getXxxString getter = serializedLambda.getImplMethodName();Method getterMethod = beanClass.getMethod(getter);Class<?> returnType = getterMethod.getReturnType();Class<?> valueClass = v.getClass();Assert.isTrue(returnType.isAssignableFrom(valueClass),String.format("required value type %s, but current type is %s", returnType.getTypeName(), valueClass.getTypeName()));Type[] genericsTypes =this.getGenericsType(getterMethod);String fieldName =this.lowerUpperCaseByFirst(getter.replace("get",""),false);if(strict){this.checkGenerics(v, genericsTypes, fieldName);}// 通过反射读取该字段值Object currentVal = beanClass.getDeclaredMethod(getter).invoke(target);// 修改值判断if(this.whetherModify(currentVal, full)){Field field = beanClass.getDeclaredField(fieldName);this.setAccessible(field);
field.set(target, v);}returnthis;}privateType[]getGenericsType(Method method){Type returnType = method.getGenericReturnType();if(returnType instanceofParameterizedType){return((ParameterizedType) returnType).getActualTypeArguments();}returnnull;}public<MextendsAccessibleObject>voidsetAccessible(M accessibleObject){if(null!= accessibleObject &&!accessibleObject.isAccessible()){
accessibleObject.setAccessible(true);}}privateStringlowerUpperCaseByFirst(String str,boolean toLower){char[] cs = str.toCharArray();if(toLower)
cs[0]-=32;else
cs[0]+=32;returnString.valueOf(cs);}privateenumFull{
NULL, EMPTY, BLANK
}}}classTester{@Data@Accessors(chain =true)publicstaticclassA{publicString aString;publicString bString;publicInteger cInteger;publicBoolean dBoolean;}@TestpublicvoidtestOk(){A a =newA().setAString("").setBString(" ");TimeInterval timer =DateUtil.timer();BeanUtil.null2Default(a).assign(A::getAString,"a null2Default").assign(A::getBString,"b null2Default").assign(A::getDBoolean,false);Console.log("耗时: {} -- {}", timer.intervalPretty(),JSONUtil.toJsonStr(a));//{"aString":"","dBoolean":false,"bString":" "}
timer.restart();BeanUtil.empty2Default(a).assign(A::getAString,"a empty2Default").assign(A::getBString,"b empty2Default").assign(A::getCInteger,2);Console.log("耗时: {} -- {}", timer.intervalPretty(),JSONUtil.toJsonStr(a));//{"aString":"a empty2Default","dBoolean":false,"bString":" ","cInteger":2}
timer.restart();BeanUtil.blank2Default(a).assign(A::getBString,"b blank2Default").assign(A::getCInteger,3);Console.log("耗时: {} -- {}", timer.intervalPretty(),JSONUtil.toJsonStr(a));//{"aString":"a blank2Default","dBoolean":false,"bString":"b blank2Default","cInteger":2}
timer.clear();}@Data@Accessors(chain =true)publicstaticclassB{List<String> aListStr;List<Object> bListObj;List<Number> cListNum;}@TestpublicvoidtestColl(){B b =newB();List<String> list_1 =CollUtil.newArrayList("1","2","3","4","5","6","7","8","9","10");List<Object> list_2 =CollUtil.newArrayList("1","2","3","4","5","6","7","8","9",10);List<Object> list_3 =CollUtil.newLinkedList(1,2,3,4L,5,6.6,7,8);TimeInterval timer =DateUtil.timer();BeanUtil.null2Default(b).cancelStrictMode().assign(B::getAListStr, list_1).assign(B::getBListObj, list_2).assign(B::getCListNum, list_3);//{"cListNum":[1,2,3,4,5,6.6,7,8],"aListStr":["1","2","3","4","5","6","7","8","9","10"],"bListObj":["1","2","3","4","5","6","7","8","9",10]}Console.log("耗时: {} -- {}", timer.intervalPretty(),JSONUtil.toJsonStr(b));
timer.clear();}@Data@Accessors(chain =true)publicstaticclassC{privateMap<String,String> aMapStrStr;privateMap<String,Integer> bMapStrInt;privateMap<String,Serializable> cMapStrSerial;}@TestpublicvoidtestMap(){Map<String,String> map_1 =MapUtil.builder("k1","v1").put("k2","v2").map();Map<String,Integer> map_2 =MapUtil.builder("k1",1).put("k2",2).map();Map<String,Serializable> map_3 =newLinkedHashMap<>();C c =newC().setCMapStrSerial(map_3);TimeInterval timer =DateUtil.timer();BeanUtil.null2Default(c).assign(C::getAMapStrStr, map_1).assign(C::getCMapStrSerial, map_3);Console.log("耗时: {} -- {}", timer.intervalPretty(),JSONUtil.toJsonStr(c));//{"cMapStrSerial":{},"aMapStrStr":{"k1":"v1","k2":"v2"}}
map_3.put("k1",123);
timer.restart();BeanUtil.empty2Default(c).assign(C::getCMapStrSerial, map_3).assign(C::getBMapStrInt, map_2);//{"bMapStrInt":{"k1":1,"k2":2},"cMapStrSerial":{"k1":123},"aMapStrStr":{"k1":"v1","k2":"v2"}}Console.log("耗时: {} -- {}", timer.intervalPretty(),JSONUtil.toJsonStr(c));
timer.clear();}// -----------------------------------------------------------------------------------------------------------------// 反例@Data@Accessors(chain =true)publicstaticclassD{publicString aString;publicA bObj;publicList<String> cListStr;privateMap<String,String> dMapStrStr;}@Testpublicvoidtest1(){D d =newD();try{BeanUtil.null2Default(d).assign(D::getAString,1);}catch(Exception e){Console.error(e.getMessage());//required value type java.lang.String, but current type is java.lang.Integer}try{BeanUtil.null2Default(d).assign(D::getBObj,newB());}catch(Exception e){Console.error(e.getMessage());//required value type com.wjh.utils.Tester$A, but current type is com.wjh.utils.Tester$B}try{BeanUtil.null2Default(d).assign(D::getCListStr,CollUtil.newArrayList("ok1","ok2",1));}catch(Exception e){Console.error(e.getMessage());//field cListStr the generic is <java.lang.String>, but there is a value of type java.lang.Integer}try{BeanUtil.null2Default(d).assign(D::getDMapStrStr,MapUtil.of("str",2));}catch(Exception e){Console.error(e.getMessage());//field dMapStrStr the generic 'value' type is <java.lang.String>, but there is a value of type java.lang.Integer}}}