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,支持:
+ *
+ * - Map
+ * - List
+ * - Bean
+ *
*
* @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