From 2e044cbda138e4bd7a93e2b43b61edb13ed1059e Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 19 Mar 2022 14:56:26 +0800 Subject: [PATCH] fix bean editor invalid bug --- CHANGELOG.md | 1 + .../java/cn/hutool/core/bean/DynaBean.java | 6 ++- .../hutool/core/bean/copier/BeanCopier.java | 18 ++++---- .../hutool/core/bean/copier/CopyOptions.java | 38 +++++------------ .../copier/provider/BeanValueProvider.java | 28 ++++++++++++- .../copier/provider/MapValueProvider.java | 42 ++++++++++++++----- .../main/java/cn/hutool/core/map/BiMap.java | 14 +++++++ .../cn/hutool/core/bean/BeanUtilTest.java | 2 +- .../cn/hutool/core/bean/Issue2202Test.java | 16 +++++-- 9 files changed, 111 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568ce15ca..d713b2d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ * 【core 】 修复NamingCase中大写转换问题(pr#572@Gitee) * 【http 】 修复GET重定向时,携带参数问题(issue#2189@Github) * 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题(pr#2188@Github) +* 【core 】 修复CopyOptions中fieldNameEditor无效问题(issue#2202@Github) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java index 15a7d4049..a26ef6d83 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java @@ -118,7 +118,11 @@ public class DynaBean extends CloneSupport implements Serializable { * @since 5.4.2 */ public boolean containsProp(String fieldName) { - return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName); + if (Map.class.isAssignableFrom(beanClass)) { + return ((Map) bean).containsKey(fieldName); + } else{ + return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName); + } } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index c3f4cb35c..6e026f856 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -121,7 +121,8 @@ public class BeanCopier implements Copier, Serializable { * @param destBean 目标Bean */ private void beanToBean(Object providerBean, Object destBean) { - valueProviderToBean(new BeanValueProvider(providerBean, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), destBean); + valueProviderToBean( + new BeanValueProvider(providerBean, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor), destBean); } /** @@ -132,7 +133,7 @@ public class BeanCopier implements Copier, Serializable { */ private void mapToBean(Map map, Object bean) { valueProviderToBean( - new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), + new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError, this.copyOptions.fieldNameEditor), bean ); } @@ -161,7 +162,7 @@ public class BeanCopier implements Copier, Serializable { return; } - // 对key做映射,映射后为null的忽略之 + // 对源key做映射,映射后为null的忽略之 key = copyOptions.editFieldName(copyOptions.getMappedFieldName(key.toString(), false)); if(null == key){ return; @@ -280,12 +281,11 @@ public class BeanCopier implements Copier, Serializable { } // 对key做映射,映射后为null的忽略之 - // 这里 copyOptions.editFieldName() 不能少,否则导致 CopyOptions setFieldNameEditor 失效 - final String providerKey = copyOptions.editFieldName(copyOptions.getMappedFieldName(fieldName, true)); - if(null == providerKey){ + final String sourceKey = copyOptions.getMappedFieldName(fieldName, true); + if(null == sourceKey){ return; } - if (false == valueProvider.containsKey(providerKey)) { + if (false == valueProvider.containsKey(sourceKey)) { // 无对应值可提供 return; } @@ -294,13 +294,13 @@ public class BeanCopier implements Copier, Serializable { final Type fieldType = TypeUtil.getActualType(this.destType ,prop.getFieldType()); // 获取属性值 - Object value = valueProvider.value(providerKey, fieldType); + Object value = valueProvider.value(sourceKey, fieldType); if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) { return; } // since 5.7.15 - value = copyOptions.editFieldValue(providerKey, value); + value = copyOptions.editFieldValue(sourceKey, value); if ((null == value && copyOptions.ignoreNullValue) || bean == value) { // 当允许跳过空时,跳过 diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java index 92ab2937a..e6a25eb92 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java @@ -1,8 +1,7 @@ package cn.hutool.core.bean.copier; import cn.hutool.core.lang.Editor; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.map.BiMap; import java.io.Serializable; import java.lang.reflect.Field; @@ -49,11 +48,7 @@ public class CopyOptions implements Serializable { /** * 拷贝属性的字段映射,用于不同的属性之前拷贝做对应表用 */ - protected Map fieldMapping; - /** - * 反向映射表,自动生成用于反向查找 - */ - private Map reversedFieldMapping; + protected BiMap fieldMapping; /** * 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等 */ @@ -215,7 +210,7 @@ public class CopyOptions implements Serializable { * @return CopyOptions */ public CopyOptions setFieldMapping(Map fieldMapping) { - this.fieldMapping = fieldMapping; + this.fieldMapping = new BiMap<>(fieldMapping); return this; } @@ -303,11 +298,14 @@ public class CopyOptions implements Serializable { * @return 映射后的字段名 */ protected String getMappedFieldName(String fieldName, boolean reversed) { - Map mapping = reversed ? getReversedMapping() : this.fieldMapping; - if (MapUtil.isEmpty(mapping)) { - return fieldName; + final BiMap fieldMapping = this.fieldMapping; + if(null != fieldMapping){ + final String mappingName = reversed ? fieldMapping.getKey(fieldName) : fieldMapping.get(fieldName); + if(null != mappingName){ + return mappingName; + } } - return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName); + return fieldName; } /** @@ -320,20 +318,4 @@ public class CopyOptions implements Serializable { protected String editFieldName(String fieldName) { return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName; } - - /** - * 获取反转之后的映射 - * - * @return 反转映射 - * @since 4.1.10 - */ - private Map getReversedMapping() { - if (null == this.fieldMapping) { - return null; - } - if (null == this.reversedFieldMapping) { - reversedFieldMapping = MapUtil.reverse(this.fieldMapping); - } - return reversedFieldMapping; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java index 3a66b0e10..a4fc982db 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java @@ -3,9 +3,12 @@ package cn.hutool.core.bean.copier.provider; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.PropDesc; import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.lang.Editor; +import cn.hutool.core.map.FuncKeyMap; import cn.hutool.core.util.StrUtil; import java.lang.reflect.Type; +import java.util.HashMap; import java.util.Map; /** @@ -27,9 +30,32 @@ public class BeanValueProvider implements ValueProvider { * @param ignoreError 是否忽略字段值读取错误 */ public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) { + this(bean, ignoreCase, ignoreError, null); + } + + /** + * 构造 + * + * @param bean Bean + * @param ignoreCase 是否忽略字段大小写 + * @param ignoreError 是否忽略字段值读取错误 + */ + public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor keyEditor) { this.source = bean; this.ignoreError = ignoreError; - sourcePdMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(ignoreCase); + final Map sourcePdMap = BeanUtil.getBeanDesc(source.getClass()).getPropMap(ignoreCase); + // issue#2202@Github + // 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key + this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (key) -> { + if (ignoreCase && key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + if (null != keyEditor) { + key = keyEditor.edit(key.toString()); + } + return key.toString(); + }); + this.sourcePdMap.putAll(sourcePdMap); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index e054c29e2..e408b6db3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -2,10 +2,12 @@ package cn.hutool.core.bean.copier.provider; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.convert.Convert; -import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.lang.Editor; +import cn.hutool.core.map.FuncKeyMap; import cn.hutool.core.util.StrUtil; import java.lang.reflect.Type; +import java.util.HashMap; import java.util.Map; /** @@ -17,13 +19,13 @@ import java.util.Map; *
  • isFirstName(如果为Boolean或boolean类型的值)
  • *
  • is_first_name(如果为Boolean或boolean类型的值)
  • * - * 为firstName或first_name都可以对应到值。 * * @author looly */ public class MapValueProvider implements ValueProvider { - private final Map map; + @SuppressWarnings("rawtypes") + private final Map map; private final boolean ignoreError; /** @@ -45,13 +47,33 @@ public class MapValueProvider implements ValueProvider { * @since 5.3.2 */ public MapValueProvider(Map map, boolean ignoreCase, boolean ignoreError) { - if (false == ignoreCase || map instanceof CaseInsensitiveMap) { - //不忽略大小写或者提供的Map本身为CaseInsensitiveMap则无需转换 - this.map = map; - } else { - //转换为大小写不敏感的Map - this.map = new CaseInsensitiveMap<>(map); - } + this(map, ignoreCase, ignoreError, null); + } + + /** + * 构造 + * + * @param map Map + * @param ignoreCase 是否忽略key的大小写 + * @param ignoreError 是否忽略错误 + * @param keyEditor 自定义键编辑器 + * @since 5.7.23 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public MapValueProvider(Map map, boolean ignoreCase, boolean ignoreError, Editor keyEditor) { + // issue#2202@Github + // 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key + this.map = new FuncKeyMap(new HashMap(map.size(), 1), (key)->{ + if(ignoreCase && key instanceof CharSequence){ + key = key.toString().toLowerCase(); + } + if(null != keyEditor){ + key = keyEditor.edit(key.toString()); + } + return key; + }); + this.map.putAll(map); + this.ignoreError = ignoreError; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java b/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java index 99be6a2f4..9f9f3fd79 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java @@ -42,6 +42,20 @@ public class BiMap extends MapWrapper { } } + @Override + public V remove(Object key) { + final V v = super.remove(key); + if(null != this.inverse && null != v){ + this.inverse.remove(v); + } + return v; + } + + @Override + public boolean remove(Object key, Object value) { + return super.remove(key, value) && null != this.inverse && this.inverse.remove(value, key); + } + @Override public void clear() { super.clear(); diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index f31190313..147056670 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -713,7 +713,7 @@ public class BeanUtilTest { CopyOptions copyOptions = CopyOptions.create(). //setIgnoreNullValue(true). //setIgnoreCase(false). - setFieldNameEditor(StrUtil::toUnderlineCase); + setFieldNameEditor(StrUtil::toCamelCase); ChildVo2 childVo2 = new ChildVo2(); BeanUtil.copyProperties(childVo1, childVo2, copyOptions); diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/Issue2202Test.java b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2202Test.java index 23ea62c45..8f6a344be 100755 --- a/hutool-core/src/test/java/cn/hutool/core/bean/Issue2202Test.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2202Test.java @@ -1,9 +1,9 @@ package cn.hutool.core.bean; import cn.hutool.core.bean.copier.CopyOptions; -import cn.hutool.core.lang.Console; import cn.hutool.core.text.NamingCase; import lombok.Data; +import org.junit.Assert; import org.junit.Test; import java.util.HashMap; @@ -11,15 +11,23 @@ import java.util.Map; public class Issue2202Test { + /** + * https://github.com/dromara/hutool/issues/2202 + */ @Test - public void toBeanWithFieldNameEditorTest(){ + public void mapToBeanWithFieldNameEditorTest(){ Map headerMap = new HashMap<>(5); headerMap.put("wechatpay-serial", "serial"); headerMap.put("wechatpay-nonce", "nonce"); headerMap.put("wechatpay-timestamp", "timestamp"); headerMap.put("wechatpay-signature", "signature"); - ResponseSignVerifyParams case1 = BeanUtil.toBean(headerMap, ResponseSignVerifyParams.class, CopyOptions.create().setFieldNameEditor(field -> NamingCase.toCamelCase(field, '-'))); - Console.log(case1); + ResponseSignVerifyParams case1 = BeanUtil.toBean(headerMap, ResponseSignVerifyParams.class, + CopyOptions.create().setFieldNameEditor(field -> NamingCase.toCamelCase(field, '-'))); + + Assert.assertEquals("serial", case1.getWechatpaySerial()); + Assert.assertEquals("nonce", case1.getWechatpayNonce()); + Assert.assertEquals("timestamp", case1.getWechatpayTimestamp()); + Assert.assertEquals("signature", case1.getWechatpaySignature()); } @Data