@SuppressWarnings("deprecation")
    public static String toString(final Object obj) {
        if (obj == null) {
            return "null";
        }

        //region Convert simple types to String directly

        if (obj instanceof CharSequence) {
            return "\"" + obj + "\"";
        }
        if (obj instanceof Character) {
            return "'" + obj + "'";
        }
        if (obj instanceof Date) {
            Date date = (Date)obj;
            long time = date.getTime();
            String dateFormat;
            if (date.getHours() == 0 && date.getMinutes() == 0 && date.getSeconds() == 0 && time % 1000 == 0) {
                dateFormat = "yyyy-MM-dd";
            } else if (time % (60 * 1000) == 0) {
                dateFormat = "yyyy-MM-dd HH:mm";
            } else if (time % 1000 == 0) {
                dateFormat = "yyyy-MM-dd HH:mm:ss";
            } else {
                dateFormat = "yyyy-MM-dd HH:mm:ss.SSS";
            }
            return new SimpleDateFormat(dateFormat).format(obj);
        }
        if (obj instanceof Enum) {
            return obj.getClass().getSimpleName() + "." + ((Enum)obj).name();
        }
        if (obj instanceof Class) {
            return ReflectionUtil.classToString((Class<?>)obj);
        }
        if (obj instanceof Field) {
            return ReflectionUtil.fieldToString((Field)obj);
        }
        if (obj instanceof Method) {
            return ReflectionUtil.methodToString((Method)obj);
        }
        if (obj instanceof Annotation) {
            return ReflectionUtil.annotationToString((Annotation)obj);
        }

        //endregion

        //region Convert the Collection and Map

        if (obj instanceof Collection) {
            return CollectionUtils.toString((Collection<?>)obj);
        }
        if (obj.getClass().isArray()) {
            return ArrayUtils.toString(obj);
        }
        if (obj instanceof Map) {
            return CollectionUtils.toString((Map<?, ?>)obj);
        }

        //endregion

        //the jdk classes
        if (obj.getClass().getClassLoader() == null) {
            return obj.toString();
        }

        return CycleDependencyHandler.wrap(obj, o -> {
            StringBuilder sb = new StringBuilder(32);

            // handle the anonymous class
            String classSimpleName;
            if (obj.getClass().isAnonymousClass()) {
                if (!obj.getClass().getSuperclass().equals(Object.class)) {
                    classSimpleName = obj.getClass().getSuperclass().getSimpleName();
                } else {
                    classSimpleName = obj.getClass().getInterfaces()[0].getSimpleName();
                }
                // Connect a '$', different from ordinary class
                classSimpleName += "$";
            } else {
                classSimpleName = obj.getClass().getSimpleName();
            }

            sb.append(classSimpleName).append("(");
            final int initialLength = sb.length();

            // Gets all fields, excluding static or synthetic fields
            Field[] fields = ReflectionUtil.getAllFields(obj.getClass());
            for (Field field : fields) {
                field.setAccessible(true);

                if (sb.length() > initialLength) {
                    sb.append(", ");
                }
                sb.append(field.getName());
                sb.append("=");
                try {
                    Object f = field.get(obj);
                    if (f == obj) {
                        sb.append("(this ").append(f.getClass().getSimpleName()).append(")");
                    } else {
                        sb.append(toString(f));
                    }
                } catch (Exception ignore) {
                }
            }

            sb.append(")");
            return sb.toString();
        });
    }
@Test
    void testToStringAndCycleDependency() throws Exception {
        //case: String
        Assertions.assertEquals("\"aaa\"", StringUtils.toString("aaa"));

        //case: CharSequence
        Assertions.assertEquals("\"bbb\"", StringUtils.toString(new StringBuilder("bbb")));
        //case: Number
        Assertions.assertEquals("1", StringUtils.toString(1));
        //case: Boolean
        Assertions.assertEquals("true", StringUtils.toString(true));
        //case: Character
        Assertions.assertEquals("'2'", StringUtils.toString('2'));
        //case: Charset
        Assertions.assertEquals("UTF-8", StringUtils.toString(StandardCharsets.UTF_8));
        //case: Thread
        try {
            Assertions.assertEquals("Thread[main,5,main]", StringUtils.toString(Thread.currentThread()));
        } catch (AssertionFailedError e) {
            // for java21 and above
            Assertions.assertEquals("Thread[#" + Thread.currentThread().getId() + ",main,5,main]", StringUtils.toString(Thread.currentThread()));
        }

        //case: Date
        Date date = new Date(2021 - 1900, 6 - 1, 15);
        Assertions.assertEquals("2021-06-15", StringUtils.toString(date));
        date.setTime(date.getTime() + 3600000);
        Assertions.assertEquals("2021-06-15 01:00", StringUtils.toString(date));
        date.setTime(date.getTime() + 60000);
        Assertions.assertEquals("2021-06-15 01:01", StringUtils.toString(date));
        date.setTime(date.getTime() + 50000);
        Assertions.assertEquals("2021-06-15 01:01:50", StringUtils.toString(date));
        date.setTime(date.getTime() + 12);
        Assertions.assertEquals("2021-06-15 01:01:50.012", StringUtils.toString(date));

        //case: Enum
        Assertions.assertEquals("ObjectHolder.INSTANCE", StringUtils.toString(ObjectHolder.INSTANCE));

        //case: Annotation
        TestAnnotation annotation = TestClass.class.getAnnotation(TestAnnotation.class);
        Assertions.assertEquals("@" + TestAnnotation.class.getSimpleName() + "(test=true)", StringUtils.toString(annotation));

        //case: Class
        Class<?> clazz = TestClass.class;
        Assertions.assertEquals("Class<" + clazz.getSimpleName() + ">", StringUtils.toString(clazz));

        //case: Method
        Method method = clazz.getMethod("setObj", TestClass.class);
        Assertions.assertEquals("Method<" + clazz.getSimpleName() + ".setObj(" + clazz.getSimpleName() + ")>", StringUtils.toString(method));

        //case: Field
        Field field = clazz.getDeclaredField("s");
        Assertions.assertEquals("Field<" + clazz.getSimpleName() + ".(String s)>", StringUtils.toString(field));

        //case: List, and cycle dependency
        List<Object> list = new ArrayList<>();
        list.add("xxx");
        list.add(111);
        list.add(list);
        Assertions.assertEquals("[\"xxx\", 111, (this ArrayList)]", StringUtils.toString(list));

        //case: String Array
        String[] strArr = new String[2];
        strArr[0] = "11";
        strArr[1] = "22";
        Assertions.assertEquals("[\"11\", \"22\"]", StringUtils.toString(strArr));
        //case: int Array
        int[] intArr = new int[2];
        intArr[0] = 11;
        intArr[1] = 22;
        Assertions.assertEquals("[11, 22]", StringUtils.toString(intArr));
        //case: Array, and cycle dependency
        Object[] array = new Object[3];
        array[0] = 1;
        array[1] = '2';
        array[2] = array;
        Assertions.assertEquals("[1, '2', (this Object[])]", StringUtils.toString(array));

        //case: Map, and cycle dependency
        Map<Object, Object> map = new HashMap<>();
        map.put("aaa", 111);
        map.put("bbb", true);
        map.put("self", map);
        Assertions.assertEquals("{\"aaa\"->111, \"bbb\"->true, \"self\"->(this HashMap)}", StringUtils.toString(map));
        Assertions.assertFalse(CycleDependencyHandler.isStarting());
        //case: Map, and cycle dependency（deep case）
        List<Object> list2 = new ArrayList<>();
        list2.add(map);
        list2.add('c');
        map.put("list", list2);
        Assertions.assertEquals("{\"aaa\"->111, \"bbb\"->true, \"self\"->(this HashMap), \"list\"->[(ref HashMap), 'c']}", StringUtils.toString(map));
        Assertions.assertFalse(CycleDependencyHandler.isStarting());


        //case: Object
        Assertions.assertEquals("CycleDependency(s=\"a\", obj=null)", StringUtils.toString(CycleDependency.A));
        //case: Object, and cycle dependency
        CycleDependency obj = new CycleDependency("c");
        obj.setObj(obj);
        Assertions.assertEquals("CycleDependency(s=\"c\", obj=(this CycleDependency))", StringUtils.toString(obj));
        //case: Object
        CycleDependency obj2 = new CycleDependency("d");
        obj.setObj(obj2);
        Assertions.assertEquals("CycleDependency(s=\"c\", obj=CycleDependency(s=\"d\", obj=null))", StringUtils.toString(obj));
        //case: Object, and cycle dependency
        TestClass a = new TestClass();
        a.setObj(a);
        Assertions.assertEquals("TestClass(obj=(this TestClass), s=null)", StringUtils.toString(a));
        //case: Object, and cycle dependency（deep case）
        TestClass b = new TestClass();
        TestClass c = new TestClass();
        b.setObj(c);
        c.setObj(a);
        a.setObj(b);
        Assertions.assertEquals("TestClass(obj=TestClass(obj=TestClass(obj=(ref TestClass), s=null), s=null), s=null)", StringUtils.toString(a));

        //case: anonymous class from an interface
        Object anonymousObj = new TestInterface() {
            private String a = "aaa";

            @Override
            public void test() {
            }
        };
        Assertions.assertEquals("TestInterface$(a=\"aaa\")", StringUtils.toString(anonymousObj));

        //case: anonymous class from an abstract class
        anonymousObj = new TestAbstractClass() {
            private String a = "aaa";

            @Override
            public void test() {
            }
        };
        Assertions.assertEquals("TestAbstractClass$(a=\"aaa\")", StringUtils.toString(anonymousObj));

        //final confirm: do not triggered the `toString` and `hashCode` methods
        Assertions.assertFalse(TestClass.hashCodeTriggered);
        Assertions.assertFalse(TestClass.toStringTriggered);
        Assertions.assertFalse(CycleDependency.hashCodeTriggered);
        Assertions.assertFalse(CycleDependency.toStringTriggered);
    }