add options

This commit is contained in:
Looly 2021-11-26 21:31:55 +08:00
parent c85f204da9
commit 95854cddb1
5 changed files with 147 additions and 18 deletions

View File

@ -26,6 +26,7 @@
* 【core 】 增加CharSequenceUtil.subPreGbk重载issue#I4JO2E@Gitee * 【core 】 增加CharSequenceUtil.subPreGbk重载issue#I4JO2E@Gitee
* 【core 】 ReflectUtil.getMethod排除桥接方法pr#1965@Github * 【core 】 ReflectUtil.getMethod排除桥接方法pr#1965@Github
* 【http 】 completeFileNameFromHeader在使用path为路径时自动解码issue#I4K0FS@Gitee * 【http 】 completeFileNameFromHeader在使用path为路径时自动解码issue#I4K0FS@Gitee
* 【core 】 CopyOptions增加override配置issue#I4JQ1N@Gitee
* *
### 🐞Bug修复 ### 🐞Bug修复
* 【core 】 修复FileResource构造fileName参数无效问题issue#1942@Github * 【core 】 修复FileResource构造fileName参数无效问题issue#1942@Github

View File

@ -121,6 +121,7 @@ public class PropDesc {
/** /**
* 检查属性是否可读即是否可以通过{@link #getValue(Object)}获取到值 * 检查属性是否可读即是否可以通过{@link #getValue(Object)}获取到值
*
* @param checkTransient 是否检查Transient关键字或注解 * @param checkTransient 是否检查Transient关键字或注解
* @return 是否可读 * @return 是否可读
* @since 5.4.2 * @since 5.4.2
@ -190,6 +191,7 @@ public class PropDesc {
/** /**
* 检查属性是否可读即是否可以通过{@link #getValue(Object)}获取到值 * 检查属性是否可读即是否可以通过{@link #getValue(Object)}获取到值
*
* @param checkTransient 是否检查Transient关键字或注解 * @param checkTransient 是否检查Transient关键字或注解
* @return 是否可读 * @return 是否可读
* @since 5.4.2 * @since 5.4.2
@ -239,7 +241,28 @@ public class PropDesc {
* @since 5.4.2 * @since 5.4.2
*/ */
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) { public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError) {
if (ignoreNull && null == value) { return setValue(bean, value, ignoreNull, ignoreError, true);
}
/**
* 设置属性值可以自动转换字段类型为目标类型
*
* @param bean Bean对象
* @param value 属性值可以为任意类型
* @param ignoreNull 是否忽略{@code null}true表示忽略
* @param ignoreError 是否忽略错误包括转换错误和注入错误
* @param override 是否覆盖目标值如果不覆盖会先读取bean的值{@code null}则写否则忽略如果覆盖则不判断直接写
* @return this
* @since 5.7.17
*/
public PropDesc setValue(Object bean, Object value, boolean ignoreNull, boolean ignoreError, boolean override) {
if (null == value && ignoreNull) {
return this;
}
// issue#I4JQ1N@Gitee
// 非覆盖模式下如果目标值存在则跳过
if (false == override && null != getValue(bean)) {
return this; return this;
} }

View File

@ -140,13 +140,44 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
* Map转Map * Map转Map
* *
* @param source 源Map * @param source 源Map
* @param dest 目标Map * @param targetMap 目标Map
*/ */
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
private void mapToMap(Map source, Map dest) { private void mapToMap(Map source, Map targetMap) {
if (null != dest && null != source) { source.forEach((key, value)->{
dest.putAll(source); final CopyOptions copyOptions = this.copyOptions;
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
// issue#I4JQ1N@Gitee
// 非覆盖模式下如果目标值存在则跳过
if(false == copyOptions.override && null != targetMap.get(key)){
return;
} }
if(key instanceof CharSequence){
if (CollUtil.contains(ignoreSet, key)) {
// 目标属性值被忽略或值提供者无此key时跳过
return;
}
// 对key做映射映射后为null的忽略之
key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false));
if(null == key){
return;
}
value = copyOptions.editFieldValue(key.toString(), value);
}
if ((null == value && copyOptions.ignoreNullValue) || source == value) {
// 当允许跳过空时跳过
//值不能为bean本身防止循环引用此类也跳过
return;
}
targetMap.put(key, value);
});
} }
/** /**
@ -158,11 +189,11 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
*/ */
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private void beanToMap(Object bean, Map targetMap) { private void beanToMap(Object bean, Map targetMap) {
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
final CopyOptions copyOptions = this.copyOptions; final CopyOptions copyOptions = this.copyOptions;
final HashSet<String> ignoreSet = (null != copyOptions.ignoreProperties) ? CollUtil.newHashSet(copyOptions.ignoreProperties) : null;
BeanUtil.descForEach(bean.getClass(), (prop)->{ BeanUtil.descForEach(bean.getClass(), (prop)->{
if(false == prop.isReadable(copyOptions.isTransientSupport())){ if(false == prop.isReadable(copyOptions.transientSupport)){
// 忽略的属性跳过之 // 忽略的属性跳过之
return; return;
} }
@ -178,6 +209,12 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
return; return;
} }
// issue#I4JQ1N@Gitee
// 非覆盖模式下如果目标值存在则跳过
if(false == copyOptions.override && null != targetMap.get(key)){
return;
}
Object value; Object value;
try { try {
value = prop.getValue(bean); value = prop.getValue(bean);
@ -230,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
// 遍历目标bean的所有属性 // 遍历目标bean的所有属性
BeanUtil.descForEach(actualEditable, (prop)->{ BeanUtil.descForEach(actualEditable, (prop)->{
if(false == prop.isWritable(this.copyOptions.isTransientSupport())){ if(false == prop.isWritable(this.copyOptions.transientSupport)){
// 字段不可写跳过之 // 字段不可写跳过之
return; return;
} }
@ -270,7 +307,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
return; return;
} }
prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError); prop.setValue(bean, value, copyOptions.ignoreNullValue, copyOptions.ignoreError, copyOptions.override);
}); });
} }
} }

View File

@ -65,7 +65,11 @@ public class CopyOptions implements Serializable {
/** /**
* 是否支持transient关键字修饰和@Transient注解如果支持被修饰的字段或方法对应的字段将被忽略 * 是否支持transient关键字修饰和@Transient注解如果支持被修饰的字段或方法对应的字段将被忽略
*/ */
private boolean transientSupport = true; protected boolean transientSupport = true;
/**
* 是否覆盖目标值如果不覆盖会先读取目标对象的值{@code null}则写否则忽略如果覆盖则不判断直接写
*/
protected boolean override = true;
/** /**
* 创建拷贝选项 * 创建拷贝选项
@ -259,7 +263,9 @@ public class CopyOptions implements Serializable {
* *
* @return 是否支持 * @return 是否支持
* @since 5.4.2 * @since 5.4.2
* @deprecated 无需此方法内部使用直接调用属性
*/ */
@Deprecated
public boolean isTransientSupport() { public boolean isTransientSupport() {
return this.transientSupport; return this.transientSupport;
} }
@ -276,6 +282,18 @@ public class CopyOptions implements Serializable {
return this; return this;
} }
/**
* 设置是否覆盖目标值如果不覆盖会先读取目标对象的值{@code null}则写否则忽略如果覆盖则不判断直接写
*
* @param override 是否覆盖目标值
* @return this
* @since 5.7.17
*/
public CopyOptions setOverride(boolean override) {
this.override = override;
return this;
}
/** /**
* 获得映射后的字段名<br> * 获得映射后的字段名<br>
* 当非反向则根据源字段名获取目标字段名反之根据目标字段名获取源字段名 * 当非反向则根据源字段名获取目标字段名反之根据目标字段名获取源字段名

View File

@ -0,0 +1,50 @@
package cn.hutool.core.bean.copier;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
public class BeanCopierTest {
/**
* 测试在非覆盖模式下目标对象有值则不覆盖
*/
@Test
public void beanToBeanNotOverrideTest() {
final A a = new A();
a.setValue("123");
final B b = new B();
b.setValue("abc");
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create().setOverride(false));
copier.copy();
Assert.assertEquals("abc", b.getValue());
}
/**
* 测试在覆盖模式下目标对象值被覆盖
*/
@Test
public void beanToBeanOverrideTest() {
final A a = new A();
a.setValue("123");
final B b = new B();
b.setValue("abc");
final BeanCopier<B> copier = BeanCopier.create(a, b, CopyOptions.create());
copier.copy();
Assert.assertEquals("123", b.getValue());
}
@Data
private static class A {
private String value;
}
@Data
private static class B {
private String value;
}
}