diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d44b5d44..a5869a38c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * 【core 】 修复CalendarUtil.isSameMonth没有判断公元前导致不一致的问题(issue#3011@Github) * 【core 】 修复WatchUtil createModify maxDepth传递后没有使用问题(issue#3005@Github) * 【core 】 修复NullComparator反转无效问题(pr#964@Gitee) +* 【setting】 修复props.toBean 数组字段未赋值问题(issue#3008@Github) ------------------------------------------------------------------------------------------------------------- # 5.8.15 (2023-03-09) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java index 6d43fbd8b..d7f1db7f5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java @@ -10,11 +10,7 @@ import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象
@@ -136,20 +132,26 @@ public class BeanPath implements Serializable { * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值 * * - * @param bean Bean、Map或List - * @param patternParts 表达式块列表 - * @param value 值 - * @return 值 + * @param bean Bean、Map或List + * @param patternParts 表达式块列表 + * @param nextNumberPart 下一个值是否 + * @param value 值 */ private void set(Object bean, List patternParts, boolean nextNumberPart, Object value) { Object subBean = this.get(patternParts, bean, true); if (null == subBean) { + // 当前节点是空,则先创建父节点 final List parentParts = getParentParts(patternParts); this.set(bean, parentParts, lastIsNumber(parentParts), nextNumberPart ? new ArrayList<>() : new HashMap<>()); //set中有可能做过转换,因此此处重新获取bean subBean = this.get(patternParts, bean, true); } - BeanUtil.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value); + + final Object newSubBean = BeanUtil.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value); + if(newSubBean != subBean){ + // 对象变更,重新加入 + this.set(bean, getParentParts(patternParts), nextNumberPart, newSubBean); + } } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index e919ad21c..376cb0aa4 100755 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -9,29 +9,12 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Editor; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.ModifierUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.*; -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.beans.PropertyEditor; -import java.beans.PropertyEditorManager; +import java.beans.*; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -116,7 +99,7 @@ public class BeanUtil { if (method.getParameterCount() == 0) { final String name = method.getName(); if (name.startsWith("get") || name.startsWith("is")) { - if(false == "getClass".equals(name)){ + if (false == "getClass".equals(name)) { return true; } } @@ -309,24 +292,32 @@ public class BeanUtil { /** * 设置字段值,通过反射设置字段值,并不调用setXXX方法
- * 对象同样支持Map类型,fieldNameOrIndex即为key + * 对象同样支持Map类型,fieldNameOrIndex即为key,支持: + * * * @param bean Bean * @param fieldNameOrIndex 字段名或序号,序号支持负数 * @param value 值 + * @return bean,当为数组时,返回一个新的数组 */ @SuppressWarnings({"unchecked", "rawtypes"}) - public static void setFieldValue(Object bean, String fieldNameOrIndex, Object value) { + public static Object setFieldValue(Object bean, String fieldNameOrIndex, Object value) { if (bean instanceof Map) { ((Map) bean).put(fieldNameOrIndex, value); } else if (bean instanceof List) { ListUtil.setOrPadding((List) bean, Convert.toInt(fieldNameOrIndex), value); } else if (ArrayUtil.isArray(bean)) { - ArrayUtil.setOrAppend(bean, Convert.toInt(fieldNameOrIndex), value); + // issue#3008,追加产生新数组,此处返回新数组 + return ArrayUtil.setOrAppend(bean, Convert.toInt(fieldNameOrIndex), value); } else { // 普通Bean对象 ReflectUtil.setFieldValue(bean, fieldNameOrIndex, value); } + return bean; } /** @@ -611,6 +602,7 @@ public class BeanUtil { } // --------------------------------------------------------------------------------------------- beanToMap + /** * 将bean的部分属性转换成map
* 可选拷贝哪些属性值,默认是不忽略值为{@code null}的值的。 @@ -623,7 +615,7 @@ public class BeanUtil { public static Map beanToMap(Object bean, String... properties) { int mapSize = 16; Editor keyEditor = null; - if(ArrayUtil.isNotEmpty(properties)){ + if (ArrayUtil.isNotEmpty(properties)) { mapSize = properties.length; final Set propertiesSet = CollUtil.set(false, properties); keyEditor = property -> propertiesSet.contains(property) ? property : null; @@ -733,7 +725,7 @@ public class BeanUtil { * @return 目标对象 */ public static T copyProperties(Object source, Class tClass, String... ignoreProperties) { - if(null == source){ + if (null == source) { return null; } T target = ReflectUtil.newInstanceIfPossible(tClass); @@ -773,7 +765,7 @@ public class BeanUtil { * @param copyOptions 拷贝选项,见 {@link CopyOptions} */ public static void copyProperties(Object source, Object target, CopyOptions copyOptions) { - if(null == source){ + if (null == source) { return; } BeanCopier.create(source, target, ObjectUtil.defaultIfNull(copyOptions, CopyOptions::create)).copy(); @@ -980,14 +972,14 @@ public class BeanUtil { /** * 判断source与target的所有公共字段的值是否相同 * - * @param source 待检测对象1 - * @param target 待检测对象2 + * @param source 待检测对象1 + * @param target 待检测对象2 * @param ignoreProperties 不需要检测的字段 * @return 判断结果,如果为true则证明所有字段的值都相同 - * @since 5.8.4 * @author Takak11 + * @since 5.8.4 */ - public static boolean isCommonFieldsEqual(Object source, Object target, String...ignoreProperties) { + public static boolean isCommonFieldsEqual(Object source, Object target, String... ignoreProperties) { if (null == source && null == target) { return true; @@ -1003,7 +995,7 @@ public class BeanUtil { sourceFields.removeAll(Arrays.asList(ignoreProperties)); for (String field : sourceFields) { - if(ObjectUtil.notEqual(sourceFieldsMap.get(field), targetFieldsMap.get(field))){ + if (ObjectUtil.notEqual(sourceFieldsMap.get(field), targetFieldsMap.get(field))) { return false; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 9a8c6ddf2..e9434487c 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -348,7 +348,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { Array.set(buffer, index, value); return buffer; } else { - if(ArrayUtil.isEmpty(buffer)){ + if (ArrayUtil.isEmpty(buffer)) { // issue#I5APJE // 可变长类型在buffer为空的情况下,类型会被擦除,导致报错,此处修正 final T[] values = newArray(value.getClass(), 1); @@ -360,7 +360,8 @@ public class ArrayUtil extends PrimitiveArrayUtil { } /** - * 将元素值设置为数组的某个位置,当给定的index大于数组长度,则追加 + * 将元素值设置为数组的某个位置,当给定的index大于数组长度,则追加
+ * 替换时返回原数组,追加时返回新数组 * * @param array 已有数组 * @param index 位置,大于长度追加,否则替换 @@ -1037,7 +1038,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { if (null == array) { return null; } - if(null == indexes){ + if (null == indexes) { return newArray(array.getClass().getComponentType(), 0); } @@ -1656,11 +1657,11 @@ public class ArrayUtil extends PrimitiveArrayUtil { * 去重数组中的元素,去重后生成新的数组,原数组不变
* 此方法通过{@link LinkedHashSet} 去重 * - * @param 数组元素类型 - * @param 唯一键类型 - * @param array 数组 + * @param 数组元素类型 + * @param 唯一键类型 + * @param array 数组 * @param uniqueGenerator 唯一键生成器 - * @param override 是否覆盖模式,如果为{@code true},加入的新值会覆盖相同key的旧值,否则会忽略新加值 + * @param override 是否覆盖模式,如果为{@code true},加入的新值会覆盖相同key的旧值,否则会忽略新加值 * @return 去重后的数组 * @since 5.8.0 */ @@ -1671,9 +1672,9 @@ public class ArrayUtil extends PrimitiveArrayUtil { } final UniqueKeySet set = new UniqueKeySet<>(true, uniqueGenerator); - if(override){ + if (override) { Collections.addAll(set, array); - } else{ + } else { for (T t : array) { set.addIfAbsent(t); } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanPathTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanPathTest.java index e0cd8731b..647ce89e5 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanPathTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanPathTest.java @@ -2,6 +2,8 @@ package cn.hutool.core.bean; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.lang.test.bean.UserInfoDict; +import cn.hutool.core.util.ArrayUtil; +import lombok.Data; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -124,4 +126,20 @@ public class BeanPathTest { beanPath.set(map, "张三"); Assert.assertEquals("{list=[[null, {name=张三}]]}", map.toString()); } + + @Test + public void appendArrayTest(){ + // issue#3008@Github + final MyUser myUser = new MyUser(); + BeanPath.create("hobby[0]").set(myUser, "LOL"); + BeanPath.create("hobby[1]").set(myUser, "KFC"); + BeanPath.create("hobby[2]").set(myUser, "COFFE"); + + Assert.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby())); + } + + @Data + static class MyUser { + private String[] hobby; + } } diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java index 6db9b9237..16dceeb57 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/Props.java @@ -585,7 +585,7 @@ public final class Props extends Properties implements BasicTypeGetter, BeanUtil.setProperty(bean, StrUtil.subSuf(key, prefix.length()), entry.getValue()); } catch (Exception e) { // 忽略注入失败的字段(这些字段可能用于其它配置) - StaticLog.debug("Ignore property: [{}]", key); + StaticLog.debug("Ignore property: [{}],because of: {}", key, e); } } diff --git a/hutool-setting/src/test/java/cn/hutool/setting/Issue3008Test.java b/hutool-setting/src/test/java/cn/hutool/setting/Issue3008Test.java new file mode 100644 index 000000000..28d86eddd --- /dev/null +++ b/hutool-setting/src/test/java/cn/hutool/setting/Issue3008Test.java @@ -0,0 +1,27 @@ +package cn.hutool.setting; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.setting.dialect.Props; +import cn.hutool.setting.dialect.PropsUtil; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +public class Issue3008Test { + + /** + * 数组字段追加后生成新的数组,造成赋值丢失
+ * 修复见:BeanUtil.setFieldValue + */ + @Test + public void toBeanTest() { + final Props props = PropsUtil.get("issue3008"); + final MyUser user = props.toBean(MyUser.class, "person"); + Assert.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(user.getHobby())); + } + + @Data + static class MyUser { + private String[] hobby; + } +} diff --git a/hutool-setting/src/test/java/cn/hutool/setting/PropsTest.java b/hutool-setting/src/test/java/cn/hutool/setting/PropsTest.java index 03da38692..7cf081368 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/PropsTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/PropsTest.java @@ -31,11 +31,11 @@ public class PropsTest { @Test public void propTest() { //noinspection MismatchedQueryAndUpdateOfCollection - Props props = new Props("test.properties"); - String user = props.getProperty("user"); + final Props props = new Props("test.properties"); + final String user = props.getProperty("user"); Assert.assertEquals(user, "root"); - String driver = props.getStr("driver"); + final String driver = props.getStr("driver"); Assert.assertEquals(driver, "com.mysql.jdbc.Driver"); } @@ -43,19 +43,19 @@ public class PropsTest { @Ignore public void propTestForAbsPAth() { //noinspection MismatchedQueryAndUpdateOfCollection - Props props = new Props("d:/test.properties"); - String user = props.getProperty("user"); + final Props props = new Props("d:/test.properties"); + final String user = props.getProperty("user"); Assert.assertEquals(user, "root"); - String driver = props.getStr("driver"); + final String driver = props.getStr("driver"); Assert.assertEquals(driver, "com.mysql.jdbc.Driver"); } @Test public void toBeanTest() { - Props props = Props.getProp("to_bean_test.properties"); + final Props props = Props.getProp("to_bean_test.properties"); - ConfigProperties cfg = props.toBean(ConfigProperties.class, "mail"); + final ConfigProperties cfg = props.toBean(ConfigProperties.class, "mail"); Assert.assertEquals("mailer@mail.com", cfg.getHost()); Assert.assertEquals(9000, cfg.getPort()); Assert.assertEquals("mailer@mail.com", cfg.getFrom()); @@ -73,14 +73,14 @@ public class PropsTest { @Test public void toBeanWithNullPrefixTest(){ - Props configProp = new Props(); + final Props configProp = new Props(); configProp.setProperty("createTime", Objects.requireNonNull(DateUtil.parse("2020-01-01"))); configProp.setProperty("isInit", true); configProp.setProperty("stairPlan", 1); configProp.setProperty("stageNum", 2); configProp.setProperty("version", 3); - SystemConfig systemConfig = configProp.toBean(SystemConfig.class); + final SystemConfig systemConfig = configProp.toBean(SystemConfig.class); Assert.assertEquals(DateUtil.parse("2020-01-01"), systemConfig.getCreateTime()); Assert.assertEquals(true, systemConfig.getIsInit()); diff --git a/hutool-setting/src/test/resources/issue3008.properties b/hutool-setting/src/test/resources/issue3008.properties new file mode 100644 index 000000000..d6b627c15 --- /dev/null +++ b/hutool-setting/src/test/resources/issue3008.properties @@ -0,0 +1,3 @@ +person.hobby[0]=LOL +person.hobby[1]=KFC +person.hobby[2]=COFFE