Compare commits

...

4 Commits

Author SHA1 Message Date
Looly
2f6662b4aa add test 2025-10-04 17:37:21 +08:00
Looly
f9ad66c934 add test 2025-10-04 16:53:59 +08:00
Looly
745ebacb4d add test 2025-10-04 16:35:13 +08:00
Looly
944e914454 fix code 2025-10-04 15:39:05 +08:00
6 changed files with 237 additions and 72 deletions

View File

@@ -69,7 +69,7 @@ public class BeanUtil {
* @since 3.0.7
*/
public static DynaBean createDynaBean(final Object bean) {
return new DynaBean(bean);
return DynaBean.of(bean);
}
/**
@@ -183,6 +183,7 @@ public class BeanUtil {
}
// endregion
// region ----- beanPath
/**
* 获取Bean中的属性值
*
@@ -200,8 +201,7 @@ public class BeanUtil {
}
// 先尝试直接获取属性
if (bean instanceof Map) {
final Map<?, ?> map = (Map<?, ?>) bean;
if (bean instanceof Map<?, ?> map) {
if (map.containsKey(expression)) {
return (T) map.get(expression);
}
@@ -222,6 +222,7 @@ public class BeanUtil {
public static void setProperty(final Object bean, final String expression, final Object value) {
BeanPath.of(expression).setValue(bean, value);
}
// endregion
// region ----- toBean
@@ -563,11 +564,11 @@ public class BeanUtil {
/**
* 给定的Bean的类名是否匹配指定类名字符串<br>
* 如果isSimple为{@code true}则只匹配类名而忽略包名例如cn.hutool.v7.TestEntity只匹配TestEntity<br>
* 如果isSimple为{@code true}则只匹配类名而忽略包名例如cn.hutool.v7.TestEntity只匹配TestEntity或者testEntity<br>
* 如果isSimple为{@code false}则匹配包括包名的全类名例如cn.hutool.v7.TestEntity匹配cn.hutool.v7.TestEntity
*
* @param bean Bean
* @param beanClassName Bean的类名
* @param beanClassName Bean的类名,首字母忽略大小写
* @param isSimple 是否只匹配类名而忽略包名true表示忽略包名
* @return 是否匹配
* @since 4.0.6
@@ -579,31 +580,7 @@ public class BeanUtil {
return ClassUtil.getClassName(bean, isSimple).equals(isSimple ? StrUtil.upperFirst(beanClassName) : beanClassName);
}
/**
* 编辑Bean的字段static字段不会处理<br>
* 例如需要对指定的字段做判空操作、null转""操作等等。
*
* @param bean bean
* @param editor 编辑器函数
* @param <T> 被编辑的Bean类型
* @return bean
* @since 5.6.4
*/
public static <T> T edit(final T bean, final UnaryOperator<Field> editor) {
if (bean == null) {
return null;
}
final Field[] fields = FieldUtil.getFields(bean.getClass());
for (final Field field : fields) {
if (ModifierUtil.isStatic(field)) {
continue;
}
editor.apply(field);
}
return bean;
}
// region ----- edit
/**
* 把Bean里面的String属性做trim操作。此方法直接对传入的Bean做修改。
* <p>
@@ -635,6 +612,33 @@ public class BeanUtil {
});
}
/**
* 编辑Bean的字段static字段不会处理<br>
* 例如需要对指定的字段做判空操作、null转""操作等等。
*
* @param bean bean
* @param editor 编辑器函数
* @param <T> 被编辑的Bean类型
* @return bean
* @since 5.6.4
*/
public static <T> T edit(final T bean, final UnaryOperator<Field> editor) {
if (bean == null) {
return null;
}
final Field[] fields = FieldUtil.getFields(bean.getClass());
for (final Field field : fields) {
if (ModifierUtil.isStatic(field)) {
continue;
}
editor.apply(field);
}
return bean;
}
// endregion
// region ----- check
/**
* 判断Bean是否为空对象空对象表示本身为{@code null}或者所有属性都为{@code null}<br>
* 此方法不判断static属性
@@ -727,7 +731,30 @@ public class BeanUtil {
return hasSetter(clazz) || hasPublicField(clazz);
}
// region ----- hasXXX
/**
* 检查Bean<br>
* 遍历Bean的字段并断言检查字段当某个字段
* 断言为{@code true} 时,返回{@code true}并不再检查后续字段;<br>
* 断言为{@code false}时,继续检查后续字段
*
* @param bean Bean
* @param predicate 断言
* @return 是否触发断言为真
*/
public static boolean checkBean(final Object bean, final Predicate<Field> predicate) {
if (null == bean) {
return true;
}
for (final Field field : FieldUtil.getFields(bean.getClass())) {
if (ModifierUtil.isStatic(field)) {
continue;
}
if (predicate.test(field)) {
return true;
}
}
return false;
}
/**
* 判断是否有Setter方法<br>
@@ -826,31 +853,6 @@ public class BeanUtil {
}
// endregion
/**
* 检查Bean<br>
* 遍历Bean的字段并断言检查字段当某个字段
* 断言为{@code true} 时,返回{@code true}并不再检查后续字段;<br>
* 断言为{@code false}时,继续检查后续字段
*
* @param bean Bean
* @param predicate 断言
* @return 是否触发断言为真
*/
public static boolean checkBean(final Object bean, final Predicate<Field> predicate) {
if (null == bean) {
return true;
}
for (final Field field : FieldUtil.getFields(bean.getClass())) {
if (ModifierUtil.isStatic(field)) {
continue;
}
if (predicate.test(field)) {
return true;
}
}
return false;
}
/**
* 获取Getter或Setter方法名对应的字段名称规则如下
* <ul>

View File

@@ -26,6 +26,7 @@ import cn.hutool.v7.core.reflect.ClassUtil;
import cn.hutool.v7.core.reflect.ConstructorUtil;
import cn.hutool.v7.core.reflect.method.MethodUtil;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@@ -39,6 +40,7 @@ import java.util.Map;
* @since 3.0.7
*/
public class DynaBean implements Cloneable, Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**

View File

@@ -120,7 +120,7 @@ public class BeanPath<T> implements Iterator<BeanPath<T>> {
}
// 每一个边界符之前的表达式是一个完整的KEY开始处理KEY
}
if (builder.length() > 0) {
if (!builder.isEmpty()) {
this.node = NodeFactory.createNode(builder.toString());
// 如果以[结束,表示后续还有表达式,需保留'['如name[0]
this.child = StrUtil.nullIfEmpty(expression.substring(c == CharUtil.BRACKET_START ? i : i + 1));

View File

@@ -187,7 +187,8 @@ public class AnnotationUtilTest {
@Test
public void testIsInherited() {
Assertions.assertFalse(AnnotationUtil.isInherited(AnnotationForTest.class));
Assertions.assertTrue(AnnotationUtil.isInherited(AnnotationForTest.class));
Assertions.assertFalse(AnnotationUtil.isInherited(MetaAnnotationForTest.class));
}
@Test

View File

@@ -16,11 +16,6 @@
package cn.hutool.v7.core.bean;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import cn.hutool.v7.core.annotation.Alias;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.bean.copier.CopyOptions;
@@ -34,16 +29,26 @@ import cn.hutool.v7.core.map.MapUtil;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.thread.ThreadUtil;
import cn.hutool.v7.core.util.ObjUtil;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
@@ -71,17 +76,15 @@ public class BeanUtilTest {
@Test
public void fillBeanTest() {
final Person person = BeanUtil.fillBean(new Person(), new ValueProvider<String>() {
final Person person = BeanUtil.fillBean(new Person(), new ValueProvider<>() {
@Override
public Object value(final String key, final Type valueType) {
switch (key) {
case "name":
return "张三";
case "age":
return 18;
}
return null;
return switch (key) {
case "name" -> "张三";
case "age" -> 18;
default -> null;
};
}
@Override
@@ -686,6 +689,7 @@ public class BeanUtilTest {
@Data
public static class HllFoodEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String bookId;
@@ -791,6 +795,7 @@ public class BeanUtilTest {
@Data
public static class PrivilegeIClassification implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String id;
@@ -891,6 +896,7 @@ public class BeanUtilTest {
@Data
public static class Student implements Serializable{
@Serial
private static final long serialVersionUID = 1L;
String name;
@@ -987,11 +993,13 @@ public class BeanUtilTest {
final Map<String, String> filedMap= new HashMap<>();
filedMap.put("name", "sourceId");
copyOptions.setFieldMapping(filedMap);
final TestPojo pojo = BeanUtil.fillBean(new TestPojo(), new ValueProvider<String>() {
final TestPojo pojo = BeanUtil.fillBean(new TestPojo(), new ValueProvider<>() {
final HashMap<String, Object> map = new HashMap<>();
{
map.put("sourceId", "123");
}
@Override
public Object value(final String key, final Type valueType) {
return map.get(key);
@@ -1011,4 +1019,138 @@ public class BeanUtilTest {
final boolean b = BeanUtil.hasGetter(Object.class);
assertFalse(b);
}
@Test
void checkBean_withNullBean_shouldReturnTrue() {
Predicate<Field> predicate = field -> true;
assertTrue(BeanUtil.checkBean(null, predicate));
}
@Test
void checkBean_withNoMatchingFields_shouldReturnFalse() {
Person bean = new Person();
Predicate<Field> predicate = field -> false;
assertFalse(BeanUtil.checkBean(bean, predicate));
}
@Test
void checkBean_withMatchingField_shouldReturnTrue() {
Person bean = new Person();
Predicate<Field> predicate = field -> "name".equals(field.getName());
assertTrue(BeanUtil.checkBean(bean, predicate));
predicate = field -> "age".equals(field.getName());
assertTrue(BeanUtil.checkBean(bean, predicate));
}
@Test
void findEditor_shouldReturnEditorForRegisteredType() {
// Given
Class<?> type = Integer.class;
// Register editor for Integer if not already registered (though usually it's pre-registered)
PropertyEditor expectedEditor = PropertyEditorManager.findEditor(type);
// When
PropertyEditor actualEditor = BeanUtil.findEditor(type);
// Then
assertNotNull(actualEditor);
assertEquals(expectedEditor.getClass(), actualEditor.getClass());
}
@Test
void findEditor_shouldReturnNullForUnregisteredType() {
// Given
class CustomUnregisteredType {}
Class<?> type = CustomUnregisteredType.class;
// When
PropertyEditor editor = BeanUtil.findEditor(type);
// Then
assertNull(editor);
}
@Test
void findEditor_shouldHandlePrimitiveTypes() {
// Given
Class<?> type = int.class;
// When
PropertyEditor editor = BeanUtil.findEditor(type);
// Then
assertNotNull(editor);
}
@Test
void descForEach_shouldProcessAllProperties() {
List<String> processedProperties = new ArrayList<>();
// 执行
BeanUtil.descForEach(Person.class, propDesc -> processedProperties.add(propDesc.getFieldName()));
// 验证
assertEquals(3, processedProperties.size());
assertTrue(processedProperties.contains("name"));
assertTrue(processedProperties.contains("age"));
assertTrue(processedProperties.contains("openid"));
}
@Test
public void testIsMatchName_SimpleMatch() {
String testObj = "test";
assertTrue(BeanUtil.isMatchName(testObj, "String", true));
assertTrue(BeanUtil.isMatchName(testObj, "string", true));
final Person person = new Person();
assertTrue(BeanUtil.isMatchName(person, "Person", true));
assertTrue(BeanUtil.isMatchName(person, "person", true));
}
@Test
public void testIsMatchName_FullMatch() {
Integer testObj = 123;
assertTrue(BeanUtil.isMatchName(testObj, "java.lang.Integer", false));
assertFalse(BeanUtil.isMatchName(testObj, "Integer", false));
final Person person = new Person();
assertTrue(BeanUtil.isMatchName(person, "cn.hutool.v7.core.bean.BeanUtilTest$Person", false));
}
@Test
void testGetPropertyDescriptor_ExistingField() {
PropertyDescriptor pd = BeanUtil.getPropertyDescriptor(Person.class, "name");
assertNotNull(pd);
assertEquals("name", pd.getName());
assertEquals(String.class, pd.getPropertyType());
}
@Test
void testGetPropertyDescriptor_NonExistingField() {
final PropertyDescriptor nonExistingField = BeanUtil.getPropertyDescriptor(Person.class, "nonExistingField");
assertNull(nonExistingField);
}
@Test
void testBeanToMap_IgnoreNullValue() {
Person person = new Person();
person.setName(null);
person.setAge(25);
// 忽略空值
Map<String, Object> map = BeanUtil.beanToMap(person, false, true);
assertEquals(1, map.size());
assertEquals(25, map.get("age"));
assertFalse(map.containsKey("name"));
// 不忽略空值
map = BeanUtil.beanToMap(person, false, false);
assertEquals(3, map.size());
assertTrue(map.containsKey("name"));
assertNull(map.get("name"));
assertEquals(25, map.get("age"));
assertTrue(map.containsKey("openid"));
assertNull(map.get("openid"));
}
}

View File

@@ -20,6 +20,9 @@ import lombok.Data;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link DynaBean}单元测试
*
@@ -27,6 +30,21 @@ import org.junit.jupiter.api.Test;
*/
public class DynaBeanTest {
@Test
void createDynaBean_withNullBean_shouldThrowException() {
assertThrows(IllegalArgumentException.class, () -> BeanUtil.createDynaBean(null));
}
@Test
void createDynaBean_withPlainObject_shouldReturnDynaBean() {
BeanUtilTest.Person bean = new BeanUtilTest.Person();
DynaBean dynaBean = BeanUtil.createDynaBean(bean);
assertNotNull(dynaBean);
assertEquals(bean, dynaBean.getBean());
assertEquals(BeanUtilTest.Person.class, dynaBean.getBeanClass());
}
@Test
public void beanTest() {
final User user = new User();