/*
 * Copyright (c) 2013-2025 Hutool Team and hutool.cn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.hutool.v7.core.bean;

import cn.hutool.v7.core.annotation.Alias;
import cn.hutool.v7.core.array.ArrayUtil;
import cn.hutool.v7.core.bean.copier.CopyOptions;
import cn.hutool.v7.core.bean.copier.ValueProvider;
import cn.hutool.v7.core.bean.path.BeanPath;
import cn.hutool.v7.core.collection.ListUtil;
import cn.hutool.v7.core.collection.set.SetUtil;
import cn.hutool.v7.core.map.Dict;
import cn.hutool.v7.core.map.MapBuilder;
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.*;

/**
 * Bean工具单元测试
 *
 * @author Looly
 */
public class BeanUtilTest {

	@Test
	public void isBeanTest() {

		// HashMap不包含setXXX方法，不是bean
		final boolean isBean = BeanUtil.isWritableBean(HashMap.class);
		assertFalse(isBean);
	}

	@Test
	public void issueI9VTZGTest() {
		final boolean bean = BeanUtil.isWritableBean(Dict.class);
		assertFalse(bean);
	}

	@Test
	public void fillBeanTest() {
		final Person person = BeanUtil.fillBean(new Person(), new ValueProvider<>() {

			@Override
			public Object value(final String key, final Type valueType) {
				return switch (key) {
					case "name" -> "张三";
					case "age" -> 18;
					default -> null;
				};
			}

			@Override
			public boolean containsKey(final String key) {
				// 总是存在key
				return true;
			}

		}, CopyOptions.of());

		assertEquals("张三", person.getName());
		assertEquals(18, person.getAge());
	}

	@Test
	public void fillBeanWithMapIgnoreCaseTest() {
		final Map<String, Object> map = MapBuilder.<String, Object>of()
				.put("Name", "Joe")
				.put("aGe", 12)
				.put("openId", "DFDFSDFWERWER")
				.build();
		final SubPerson person = BeanUtil.fillBeanWithMap(
			map, new SubPerson(), CopyOptions.of().setIgnoreCase(true));
		assertEquals("Joe", person.getName());
		assertEquals(12, person.getAge());
		assertEquals("DFDFSDFWERWER", person.getOpenid());
	}

	@Test
	public void toBeanTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Map<?, ?> map = BeanUtil.toBean(person, Map.class);
		assertEquals("测试A11", map.get("name"));
		assertEquals(14, map.get("age"));
		assertEquals("11213232", map.get("openid"));
		// static属性应被忽略
		assertFalse(map.containsKey("SUBNAME"));
	}

	/**
	 * 忽略转换错误测试
	 */
	@Test
	public void toBeanIgnoreErrorTest() {
		final HashMap<String, Object> map = MapUtil.newHashMap();
		map.put("name", "Joe");
		// 错误的类型，此处忽略
		map.put("age", "aaaaaa");

		final Person person = BeanUtil.toBean(map, Person.class, CopyOptions.of().setIgnoreError(true));
		assertEquals("Joe", person.getName());
		// 错误的类型，不copy这个字段，使用对象创建的默认值
		assertEquals(0, person.getAge());
	}

	@Test
	public void mapToBeanIgnoreCaseTest() {
		final HashMap<String, Object> map = MapUtil.newHashMap();
		map.put("Name", "Joe");
		map.put("aGe", 12);

		final Person person = BeanUtil.toBean(map, Person.class, CopyOptions.of().setIgnoreCase(true));
		assertEquals("Joe", person.getName());
		assertEquals(12, person.getAge());
	}

	@Test
	public void mapToBeanTest() {
		final HashMap<String, Object> map = MapUtil.newHashMap();
		map.put("a_name", "Joe");
		map.put("b_age", 12);

		// 别名，用于对应bean的字段名
		final HashMap<String, String> mapping = MapUtil.newHashMap();
		mapping.put("a_name", "name");
		mapping.put("b_age", "age");

		final Person person = BeanUtil.toBean(map, Person.class, CopyOptions.of().setFieldMapping(mapping));
		assertEquals("Joe", person.getName());
		assertEquals(12, person.getAge());
	}

	/**
	 * 测试public类型的字段注入是否成功
	 */
	@Test
	public void mapToBeanTest2() {
		final HashMap<String, Object> map = MapUtil.newHashMap();
		map.put("name", "Joe");
		map.put("age", 12);

		// 非空构造也可以实例化成功
		final Person2 person = BeanUtil.toBean(map, Person2.class, CopyOptions.of());
		assertEquals("Joe", person.name);
		assertEquals(12, person.age);
	}

	/**
	 * 测试在不忽略错误情况下，转换失败需要报错。
	 */
	@Test
	public void mapToBeanWinErrorTest() {
		Assertions.assertThrows(NumberFormatException.class, ()->{
			final Map<String, String> map = new HashMap<>();
			map.put("age", "哈哈");
			BeanUtil.toBean(map, Person.class);
		});
	}

	@Test
	public void beanToMapTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Map<String, Object> map = BeanUtil.beanToMap(person);

		assertEquals("测试A11", map.get("name"));
		assertEquals(14, map.get("age"));
		assertEquals("11213232", map.get("openid"));
		// static属性应被忽略
		assertFalse(map.containsKey("SUBNAME"));
	}

	@Test
	public void toBeanMapTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Map<String, Object> map = BeanUtil.toBeanMap(person);

		assertEquals("测试A11", map.get("name"));
		assertEquals(14, map.get("age"));
		assertEquals("11213232", map.get("openid"));
		// static属性应被忽略
		assertFalse(map.containsKey("SUBNAME"));
	}

	@Test
	public void beanToMapNullPropertiesTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Map<String, Object> map = BeanUtil.beanToMap(person, (String[])null);

		assertEquals("测试A11", map.get("name"));
		assertEquals(14, map.get("age"));
		assertEquals("11213232", map.get("openid"));
		// static属性应被忽略
		assertFalse(map.containsKey("SUBNAME"));
	}

	@Test
	public void beanToMapTest2() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Map<String, Object> map = BeanUtil.beanToMap(person, true, true);
		assertEquals("sub名字", map.get("sub_name"));
	}

	@Test
	public void beanToMapWithValueEditTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Map<String, Object> map = BeanUtil.beanToMap(person, new LinkedHashMap<>(),
				CopyOptions.of().setFieldEditor((entry) -> {
					entry.setValue(entry.getKey() + "_" + entry.getValue());
					return entry;
				}));
		assertEquals("subName_sub名字", map.get("subName"));
	}

	@Test
	public void beanToMapWithAliasTest() {
		final SubPersonWithAlias person = new SubPersonWithAlias();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");
		person.setSlow(true);
		person.setBooleana(true);
		person.setBooleanb(true);

		final Map<String, Object> map = BeanUtil.beanToMap(person);
		assertEquals("sub名字", map.get("aliasSubName"));
	}

	@Test
	public void mapToBeanWithAliasTest() {
		final Map<String, Object> map = MapUtil.newHashMap();
		map.put("aliasSubName", "sub名字");
		map.put("slow", true);
		map.put("is_booleana", "1");
		map.put("is_booleanb", true);

		final SubPersonWithAlias subPersonWithAlias = BeanUtil.toBean(map, SubPersonWithAlias.class);
		assertEquals("sub名字", subPersonWithAlias.getSubName());

		// https://gitee.com/chinabugotech/hutool/issues/I6H0XF
		// is_booleana并不匹配booleana字段
		assertFalse(subPersonWithAlias.isBooleana());
		Assertions.assertNull(subPersonWithAlias.getBooleanb());
	}

	@Test
	public void beanToMapWithLocalDateTimeTest() {
		final LocalDateTime now = LocalDateTime.now();

		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");
		person.setDate(now);
		person.setDate2(now.toLocalDate());

		final Map<String, Object> map = BeanUtil.beanToMap(person, false, true);
		assertEquals(now, map.get("date"));
		assertEquals(now.toLocalDate(), map.get("date2"));
	}

	@Test
	public void getPropertyTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final Object name = BeanUtil.getProperty(person, "name");
		assertEquals("测试A11", name);
		final Object subName = BeanUtil.getProperty(person, "subName");
		assertEquals("sub名字", subName);
	}

	@Test
	public void getPropertyWithClassNameTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		// 获取Bean属性时，如果用户传入名称以对象名开头，则自动去掉对象名，获取剩余部分的属性值
		final Object name = BeanUtil.getProperty(person, "subPerson.name");
		assertEquals("测试A11", name);
		final Object subName = BeanUtil.getProperty(person, "subPerson.subName");
		assertEquals("sub名字", subName);
	}

	@Test
	@SuppressWarnings("ConstantConditions")
	public void getNullPropertyTest() {
		final Object property = BeanUtil.getProperty(null, "name");
		Assertions.assertNull(property);
	}

	@Test
	public void getPropertyDescriptorsTest() {
		final HashSet<Object> set = SetUtil.of();
		final PropertyDescriptor[] propertyDescriptors = BeanUtil.getPropertyDescriptors(SubPerson.class);
		for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
			set.add(propertyDescriptor.getName());
		}
		assertTrue(set.contains("age"));
		assertTrue(set.contains("id"));
		assertTrue(set.contains("name"));
		assertTrue(set.contains("openid"));
		assertTrue(set.contains("slow"));
		assertTrue(set.contains("subName"));
	}

	@Test
	public void copyPropertiesTest() {
		final SubPerson person = new SubPerson();
		person.setAge(14);
		person.setOpenid("11213232");
		person.setName("测试A11");
		person.setSubName("sub名字");

		final SubPerson person1 = BeanUtil.copyProperties(person, SubPerson.class);
		assertEquals(14, person1.getAge());
		assertEquals("11213232", person1.getOpenid());
		assertEquals("测试A11", person1.getName());
		assertEquals("sub名字", person1.getSubName());
	}

	@Test
	@Disabled
	public void multiThreadTest(){
		final Student student = new Student();
		student.setName("张三");
		student.setAge(123);
		student.setNo(3158L);

		final Student student2 = new Student();
		student2.setName("李四");
		student2.setAge(125);
		student2.setNo(8848L);

		final List<Student> studentList = ListUtil.view(student, student2);

		for (int i=0;i<5000;i++){
			new Thread(()->{
				final List<Student> list = ObjUtil.clone(studentList);
				final List<Student> listReps = list.stream().map(s1 -> {
					final Student s2 = new Student();
					BeanUtil.copyProperties(s1, s2);
					return s2;
				}).collect(Collectors.toList());

				System.out.println(listReps);
			}).start();
		}

		ThreadUtil.waitForDie();
	}

	@Test
	public void copyPropertiesHasBooleanTest() {
		final SubPerson p1 = new SubPerson();
		p1.setSlow(true);

		// 测试boolean参数值isXXX形式
		final SubPerson p2 = new SubPerson();
		BeanUtil.copyProperties(p1, p2);
		assertTrue(p2.getSlow());

		// 测试boolean参数值非isXXX形式
		final SubPerson2 p3 = new SubPerson2();
		BeanUtil.copyProperties(p1, p3);
		assertTrue(p3.getSlow());
	}

	@Test
	public void copyPropertiesIgnoreNullTest() {
		final SubPerson p1 = new SubPerson();
		p1.setSlow(true);
		p1.setName(null);

		final SubPerson2 p2 = new SubPerson2();
		p2.setName("oldName");

		// null值不覆盖目标属性
		BeanUtil.copyProperties(p1, p2, CopyOptions.of().setIgnoreNullValue(true));
		assertEquals("oldName", p2.getName());

		// null覆盖目标属性
		BeanUtil.copyProperties(p1, p2);
		Assertions.assertNull(p2.getName());
	}

	@Test
	public void copyPropertiesBeanToMapTest() {
		// 测试BeanToMap
		final SubPerson p1 = new SubPerson();
		p1.setSlow(true);
		p1.setName("测试");
		p1.setSubName("sub测试");

		final Map<String, Object> map = MapUtil.newHashMap();
		BeanUtil.copyProperties(p1, map);
		assertTrue((Boolean) map.get("slow"));
		assertEquals("测试", map.get("name"));
		assertEquals("sub测试", map.get("subName"));
	}

	@Test
	public void copyPropertiesMapToMapTest() {
		// 测试MapToMap
		final Map<String, Object> p1 = new HashMap<>();
		p1.put("isSlow", true);
		p1.put("name", "测试");
		p1.put("subName", "sub测试");

		final Map<String, Object> map = MapUtil.newHashMap();
		BeanUtil.copyProperties(p1, map);
		assertTrue((Boolean) map.get("isSlow"));
		assertEquals("测试", map.get("name"));
		assertEquals("sub测试", map.get("subName"));
	}

	@Test
	public void trimBeanStrFieldsTest() {
		final Person person = new Person();
		person.setAge(1);
		person.setName("  张三 ");
		person.setOpenid(null);
		final Person person2 = BeanUtil.trimStrFields(person);

		// 是否改变原对象
		assertEquals("张三", person.getName());
		assertEquals("张三", person2.getName());
	}

	// -----------------------------------------------------------------------------------------------------------------
	@Getter
	@Setter
	public static class SubPerson extends Person {

		public static final String SUBNAME = "TEST";

		private UUID id;
		private String subName;
		private Boolean slow;
		private LocalDateTime date;
		private LocalDate date2;
	}

	@Getter
	@Setter
	public static class SubPerson2 extends Person {
		private String subName;
		// boolean参数值非isXXX形式
		private Boolean slow;
	}

	@Getter
	@Setter
	@ToString
	public static class SubPersonWithAlias extends Person {
		// boolean参数值非isXXX形式
		@Alias("aliasSubName")
		private String subName;
		private Boolean slow;
		private boolean booleana;
		private Boolean booleanb;
	}

	@Getter
	@Setter
	public static class SubPersonWithOverlayTransientField extends PersonWithTransientField {
		// 覆盖父类中 transient 属性
		private String name;
	}

	@Getter
	@Setter
	public static class Person {
		private String name;
		private int age;
		private String openid;
	}

	@Getter
	@Setter
	public static class PersonWithTransientField {
		private transient String name;
		private int age;
		private String openid;
	}

	public static class Person2 {

		public Person2(final String name, final int age, final String openid) {
			this.name = name;
			this.age = age;
			this.openid = openid;
		}

		public final String name;
		public int age;
		public String openid;
	}

	/**
	 * <a href="https://github.com/chinabugotech/hutool/issues/1173">#1173</a>
	 */
	@Test
	public void beanToBeanOverlayFieldTest() {
		final SubPersonWithOverlayTransientField source = new SubPersonWithOverlayTransientField();
		source.setName("zhangsan");
		source.setAge(20);
		source.setOpenid("1");
		final SubPersonWithOverlayTransientField dest = new SubPersonWithOverlayTransientField();
		BeanUtil.copyProperties(source, dest);

		assertEquals(source.getName(), dest.getName());
		assertEquals(source.getAge(), dest.getAge());
		assertEquals(source.getOpenid(), dest.getOpenid());
	}

	@Test
	public void beanToBeanTest() {
		// 修复对象无getter方法导致报错的问题
		final Page page1 = new Page();
		BeanUtil.toBean(page1, Page.class);
	}

	public static class Page {
		private boolean optimizeCountSql = true;

		public boolean optimizeCountSql() {
			return optimizeCountSql;
		}

		public Page setOptimizeCountSql(final boolean optimizeCountSql) {
			this.optimizeCountSql = optimizeCountSql;
			return this;
		}
	}

	@Test
	public void copyBeanToBeanTest() {
		// 测试在copyProperties方法中alias是否有效
		final Food info = new Food();
		info.setBookID("0");
		info.setCode("123");
		final HllFoodEntity entity = new HllFoodEntity();
		BeanUtil.copyProperties(info, entity);
		assertEquals(info.getBookID(), entity.getBookId());
		assertEquals(info.getCode(), entity.getCode2());
	}

	@Test
	public void copyBeanTest() {
		final Food info = new Food();
		info.setBookID("0");
		info.setCode("123");
		final Food newFood = BeanUtil.copyProperties(info, Food.class, "code");
		assertEquals(info.getBookID(), newFood.getBookID());
		Assertions.assertNull(newFood.getCode());
	}

	@Test
	public void copyNullTest() {
		Assertions.assertNull(BeanUtil.copyProperties(null, Food.class));
	}

	@Test
	public void copyPropertiesMapToMapIgnoreNullTest() {
		// 测试MapToMap
		final Map<String, Object> p1 = new HashMap<>();
		p1.put("isSlow", true);
		p1.put("name", "测试");
		p1.put("subName", null);

		final Map<String, Object> map = MapUtil.newHashMap();
		BeanUtil.copyProperties(p1, map, CopyOptions.of().setIgnoreNullValue(true));
		assertTrue((Boolean) map.get("isSlow"));
		assertEquals("测试", map.get("name"));
		assertFalse(map.containsKey("subName"));
	}

	@Test
	public void copyBeanPropertiesFilterTest() {
		final Food info = new Food();
		info.setBookID("0");
		info.setCode("");
		final Food newFood = new Food();
		final CopyOptions copyOptions = CopyOptions.of().setPropertiesFilter((f, v) -> !(v instanceof CharSequence) || StrUtil.isNotBlank(v.toString()));
		BeanUtil.copyProperties(info, newFood, copyOptions);
		assertEquals(info.getBookID(), newFood.getBookID());
		Assertions.assertNull(newFood.getCode());
	}

	@Test
	public void copyBeanPropertiesFunctionFilterTest() {
		//https://gitee.com/chinabugotech/hutool/pulls/590
		final Person o = new Person();
		o.setName("asd");
		o.setAge(123);
		o.setOpenid("asd");

		@SuppressWarnings("unchecked")
		final CopyOptions copyOptions = CopyOptions.of().setIgnoreProperties(Person::getAge,Person::getOpenid);
		final Person n = new Person();
		BeanUtil.copyProperties(o, n, copyOptions);

		// 是否忽略拷贝属性
		Assertions.assertNotEquals(o.getAge(),n.getAge());
		Assertions.assertNotEquals(o.getOpenid(),n.getOpenid());
	}

	@Data
	public static class Food {
		@Alias("bookId")
		private String bookID;
		private String code;
	}

	@Data
	public static class HllFoodEntity implements Serializable {
		@Serial
		private static final long serialVersionUID = 1L;

		private String bookId;
		@Alias("code")
		private String code2;
	}

	@Test
	public void setPropertiesTest() {
		final Map<String, Object> resultMap = MapUtil.newHashMap();
		BeanUtil.setProperty(resultMap, "codeList[0].name", "张三");
		assertEquals("{codeList=[{name=张三}]}", resultMap.toString());
	}

	@Test
	public void beanCopyTest() {
		final Station station = new Station();
		station.setId(123456L);

		final Station station2 = new Station();

		BeanUtil.copyProperties(station, station2);
		assertEquals(Long.valueOf(123456L), station2.getId());
	}

	static class Station extends Tree<Long> {}
	static class Tree<T> extends Entity<T> {}

	@Data
	public static class Entity<T> {
		private T id;
	}

	@Test
	public void copyListTest() {
		final Student student = new Student();
		student.setName("张三");
		student.setAge(123);
		student.setNo(3158L);

		final Student student2 = new Student();
		student2.setName("李四");
		student2.setAge(125);
		student2.setNo(8848L);

		final List<Student> studentList = ListUtil.view(student, student2);
		final List<Person> people = BeanUtil.copyToList(studentList, Person.class);

		assertEquals(studentList.size(), people.size());
		for (int i = 0; i < studentList.size(); i++) {
			assertEquals(studentList.get(i).getName(), people.get(i).getName());
			assertEquals(studentList.get(i).getAge(), people.get(i).getAge());
		}

	}

	@Test
	public void copyListTest2() {
		final Student student = new Student();
		student.setName("张三");
		student.setAge(123);
		student.setNo(3158L);

		final Student student2 = new Student();
		student2.setName("李四");
		student2.setAge(125);
		student2.setNo(8848L);

		final List<Student> studentList = ListUtil.view(student, student2);
		final List<Person> people = BeanUtil.copyToList(studentList, Person.class, CopyOptions.of().setFieldMapping(MapUtil.of("no", "openid")));

		assertEquals(studentList.size(), people.size());
		for (int i = 0; i < studentList.size(); i++) {
			assertEquals(studentList.get(i).getName(), people.get(i).getName());
			assertEquals(studentList.get(i).getAge(), people.get(i).getAge());
			assertEquals(studentList.get(i).getNo().toString(), people.get(i).getOpenid());
		}

	}

	@Test
	public void toMapTest() {
		// 测试转map的时候返回key
		final PrivilegeIClassification a = new PrivilegeIClassification();
		a.setId("1");
		a.setName("2");
		a.setCode("3");
		a.setCreateTime(new Date());
		a.setSortOrder(9L);

		final Map<String, Object> f = BeanUtil.beanToMap(
				a,
				new LinkedHashMap<>(),
				false,
				entry -> {
					if(!ArrayUtil.contains(new String[]{"id", "name", "code", "sortOrder"}, entry.getKey())){
						entry.setKey(null);
					}
					return entry;
				});
		assertFalse(f.containsKey(null));
	}

	@Data
	public static class PrivilegeIClassification implements Serializable {
		@Serial
		private static final long serialVersionUID = 1L;

		private String id;
		private String name;
		private String code;
		private Long rowStatus;
		private Long sortOrder;
		private Date createTime;
	}

	@Test
	public void getFieldValue() {
		final TestPojo testPojo = new TestPojo();
		testPojo.setName("名字");

		final TestPojo2 testPojo2 = new TestPojo2();
		testPojo2.setAge(2);
		final TestPojo2 testPojo3 = new TestPojo2();
		testPojo3.setAge(3);


		testPojo.setTestPojo2List(new TestPojo2[]{testPojo2, testPojo3});

		final BeanPath<Object> beanPath = BeanPath.of("testPojo2List.age");
		final Object o = beanPath.getValue(testPojo);

		assertEquals(Integer.valueOf(2), ArrayUtil.get(o, 0));
		assertEquals(Integer.valueOf(3), ArrayUtil.get(o, 1));
	}

	@Test
	void hasNullFieldTest() {
		assertTrue(BeanUtil.hasNullField(null));


		final TestPojo testPojo = new TestPojo();
		assertTrue(BeanUtil.hasNullField(testPojo));

		testPojo.setName("test");
		assertTrue(BeanUtil.hasNullField(testPojo));
		// 忽略testPojo2List，则只剩下name属性，非空，返回false
		assertFalse(BeanUtil.hasNullField(testPojo, "testPojo2List"));

		testPojo.setTestPojo2List(new TestPojo2[0]);
		// 所有字段都有值
		assertFalse(BeanUtil.hasNullField(testPojo));
	}

	@Test
	void hasEmptyFieldTest() {
		assertTrue(BeanUtil.hasEmptyField(null));


		final TestPojo testPojo = new TestPojo();
		assertTrue(BeanUtil.hasEmptyField(testPojo));

		testPojo.setName("test");
		assertTrue(BeanUtil.hasEmptyField(testPojo));
		// 忽略testPojo2List，则只剩下name属性，非空，返回false
		assertFalse(BeanUtil.hasEmptyField(testPojo, "testPojo2List"));

		testPojo.setTestPojo2List(new TestPojo2[0]);
		// 所有字段都有值
		assertFalse(BeanUtil.hasEmptyField(testPojo));

		// 给空字段值
		testPojo.setName("");
		assertTrue(BeanUtil.hasEmptyField(testPojo));
	}

	@Test
	void isEmptyTest() {
		assertTrue(BeanUtil.isEmpty(null));

		final TestPojo testPojo = new TestPojo();
		assertTrue(BeanUtil.isEmpty(testPojo));

		testPojo.setName("test");
		assertFalse(BeanUtil.isEmpty(testPojo));
		// 忽略name属性判断
		assertTrue(BeanUtil.isEmpty(testPojo, "name"));

		testPojo.setTestPojo2List(new TestPojo2[0]);
		// 所有字段都有值
		assertFalse(BeanUtil.isEmpty(testPojo));
	}

	@Data
	public static class TestPojo {
		private String name;
		private TestPojo2[] testPojo2List;
	}

	@Data
	public static class TestPojo2 {
		private int age;
	}

	@Data
	public static class Student implements Serializable{
		@Serial
		private static final long serialVersionUID = 1L;

		String name;
		int age;
		Long no;
	}

	/**
	 * @author dazer
	 *  copyProperties(Object source, Object target, CopyOptions copyOptions)
	 *  当：copyOptions的 setFieldNameEditor 不为空的时候，有bug,这里进行修复；
	 */
	@Test
	public void beanToBeanCopyOptionsTest() {
		final ChildVo1 childVo1 = new ChildVo1();
		childVo1.setChild_address("中国北京五道口");
		childVo1.setChild_name("张三");
		childVo1.setChild_father_name("张无忌");
		childVo1.setChild_mother_name("赵敏敏");

		final CopyOptions copyOptions = CopyOptions.of().
				//setIgnoreNullValue(true).
				//setIgnoreCase(false).
						setFieldEditor(entry->{
							entry.setKey(StrUtil.toCamelCase(entry.getKey().toString()));
							return entry;
				});

		final ChildVo2 childVo2 = new ChildVo2();
		BeanUtil.copyProperties(childVo1, childVo2, copyOptions);

		assertEquals(childVo1.getChild_address(), childVo2.getChildAddress());
		assertEquals(childVo1.getChild_name(), childVo2.getChildName());
		assertEquals(childVo1.getChild_father_name(), childVo2.getChildFatherName());
		assertEquals(childVo1.getChild_mother_name(), childVo2.getChildMotherName());
	}

	@Data
	public static class ChildVo1 {
		String child_name;
		String child_address;
		String child_mother_name;
		String child_father_name;
	}

	@Data
	public static class ChildVo2 {
		String childName;
		String childAddress;
		String childMotherName;
		String childFatherName;
	}

	@Test
	public void issueI41WKPTest(){
		final Test1 t1 = new Test1().setStrList(ListUtil.of("list"));
		final Test2 t2_hu = new Test2();
		BeanUtil.copyProperties(t1, t2_hu, CopyOptions.of().setIgnoreError(true));
		Assertions.assertNull(t2_hu.getStrList());
	}

	@Data
	@Accessors(chain = true)
	public static class Test1 {
		private List<String> strList;
	}

	@Data
	@Accessors(chain = true)
	public static class Test2 {
		private List<Integer> strList;
	}

	@Test
	public void issuesI53O9JTest(){
		final Map<String, String> map = new HashMap<>();
		map.put("statusIdUpdateTime", "");

		final WkCrmCustomer customer = new WkCrmCustomer();
		BeanUtil.copyProperties(map, customer);

		Assertions.assertNull(customer.getStatusIdUpdateTime());
	}

	@Data
	public static class WkCrmCustomer{
		private LocalDateTime statusIdUpdateTime;
	}

	@Test
	public void valueProviderToBeanTest(){
		// https://gitee.com/chinabugotech/hutool/issues/I5B4R7
		final CopyOptions copyOptions = CopyOptions.of();
		final Map<String, String> filedMap= new HashMap<>();
		filedMap.put("name", "sourceId");
		copyOptions.setFieldMapping(filedMap);
		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);
			}

			@Override
			public boolean containsKey(final String key) {
				return map.containsKey(key);
			}
		}, copyOptions);
		assertEquals("123", pojo.getName());
	}

	@Test
	public void hasGetterTest() {
		// https://gitee.com/chinabugotech/hutool/issues/I6M7Z7
		final boolean b = BeanUtil.hasGetter(Object.class);
		assertFalse(b);
	}

	@Test
	void checkBean_withNullBean_shouldReturnTrue() {
		final Predicate<Field> predicate = field -> true;
		assertTrue(BeanUtil.checkBean(null, predicate));
	}

	@Test
	void checkBean_withNoMatchingFields_shouldReturnFalse() {
		final Person bean = new Person();
		final Predicate<Field> predicate = field -> false;
		assertFalse(BeanUtil.checkBean(bean, predicate));
	}

	@Test
	void checkBean_withMatchingField_shouldReturnTrue() {
		final 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
		final Class<?> type = Integer.class;
		// Register editor for Integer if not already registered (though usually it's pre-registered)
		final PropertyEditor expectedEditor = PropertyEditorManager.findEditor(type);

		// When
		final PropertyEditor actualEditor = BeanUtil.findEditor(type);

		// Then
		assertNotNull(actualEditor);
		assertEquals(expectedEditor.getClass(), actualEditor.getClass());
	}

	@Test
	void findEditor_shouldReturnNullForUnregisteredType() {
		// Given
		class CustomUnregisteredType {}
		final Class<?> type = CustomUnregisteredType.class;

		// When
		final PropertyEditor editor = BeanUtil.findEditor(type);

		// Then
		assertNull(editor);
	}

	@Test
	void findEditor_shouldHandlePrimitiveTypes() {
		// Given
		final Class<?> type = int.class;

		// When
		final PropertyEditor editor = BeanUtil.findEditor(type);

		// Then
		assertNotNull(editor);
	}

	@Test
	void descForEach_shouldProcessAllProperties() {
		final 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() {
		final 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() {
		final 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() {
		final 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() {
		final 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"));
	}
}
