001/*
002 *  Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.agentsflex.core.util;
017
018import com.alibaba.fastjson2.JSON;
019import com.alibaba.fastjson2.JSONArray;
020import com.alibaba.fastjson2.JSONObject;
021import com.alibaba.fastjson2.JSONWriter;
022
023import java.lang.reflect.ParameterizedType;
024import java.lang.reflect.Type;
025import java.math.BigDecimal;
026import java.math.BigInteger;
027import java.time.Instant;
028import java.time.LocalDate;
029import java.time.LocalDateTime;
030import java.time.ZoneId;
031import java.time.format.DateTimeFormatter;
032import java.time.format.DateTimeFormatterBuilder;
033import java.time.format.DateTimeParseException;
034import java.time.temporal.ChronoField;
035import java.time.temporal.TemporalAccessor;
036import java.util.*;
037import java.util.concurrent.ConcurrentHashMap;
038import java.util.regex.Pattern;
039
040/**
041 * 高性能通用类型转换工具类
042 * <p>
043 * 转换策略优先级(按性能排序):
044 * <ol>
045 *   <li>null 处理 & 类型匹配 → 直接返回</li>
046 *   <li>基本类型 & 包装类型 → 直接转换(零 JSON 开销)</li>
047 *   <li>String/Number/Boolean → 类型安全转换</li>
048 *   <li>Date/Time/Enum → 特殊处理(多格式解析 + 缓存)</li>
049 *   <li>复杂对象/泛型集合 → 走 JSON 转换(兜底)</li>
050 * </ol>
051 * <p>
052 * 线程安全:本类所有方法均为静态无状态,可安全并发调用
053 *
054 * @author Michael
055 * @since 1.0
056 */
057public class TypeConverter {
058
059    // ========== 常量定义 ==========
060
061    /**
062     * 基本类型 → 包装类型映射
063     */
064    private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_MAP = new HashMap<>(8);
065
066    static {
067        PRIMITIVE_WRAPPER_MAP.put(boolean.class, Boolean.class);
068        PRIMITIVE_WRAPPER_MAP.put(byte.class, Byte.class);
069        PRIMITIVE_WRAPPER_MAP.put(char.class, Character.class);
070        PRIMITIVE_WRAPPER_MAP.put(short.class, Short.class);
071        PRIMITIVE_WRAPPER_MAP.put(int.class, Integer.class);
072        PRIMITIVE_WRAPPER_MAP.put(long.class, Long.class);
073        PRIMITIVE_WRAPPER_MAP.put(float.class, Float.class);
074        PRIMITIVE_WRAPPER_MAP.put(double.class, Double.class);
075    }
076
077    /**
078     * 数值类型集合
079     */
080    private static final Set<Class<?>> NUMBER_TYPES = new HashSet<>(Arrays.asList(
081        Byte.class, Short.class, Integer.class, Long.class,
082        Float.class, Double.class, BigDecimal.class, BigInteger.class
083    ));
084
085    /**
086     * Boolean 真值集合(不区分大小写)
087     */
088    private static final Set<String> TRUE_VALUES = new HashSet<>(Arrays.asList("true", "1", "yes", "y", "on"));
089    /**
090     * Boolean 假值集合(不区分大小写)
091     */
092    private static final Set<String> FALSE_VALUES = new HashSet<>(Arrays.asList("false", "0", "no", "n", "off", ""));
093
094    /**
095     * 纯整数正则:可选符号 + 数字
096     */
097    private static final Pattern PURE_INTEGER_PATTERN = Pattern.compile("^[+-]?\\d+$");
098
099    /**
100     * 时间戳正则:10位(秒) 或 13位(毫秒)
101     */
102    private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("^\\d{10,13}$");
103
104    /**
105     * 日期格式列表(按命中率降序排列,提升平均性能)
106     */
107    private static final String[] DATE_PATTERNS = {
108        // 高频格式放前面
109        "yyyy-MM-dd HH:mm:ss",
110        "yyyy-MM-dd",
111        "yyyy-MM-dd HH:mm:ss.SSS",
112        "yyyy/MM/dd HH:mm:ss",
113        "yyyy/MM/dd",
114        "yyyy-MM-dd HH:mm",
115        "yyyy.MM.dd HH:mm:ss",
116        "yyyy.MM.dd",
117        "yyyy年MM月dd日",
118        "HH:mm:ss",
119        "HH:mm:ss.SSS",
120        "HH:mm",
121        "yyyy-MM-dd hh:mm:ss a",
122        "yyyy-MM-dd hh:mm:ss.SSS a",
123        "hh:mm:ss a",
124        "yyyyMMdd",
125        "yyyyMMddHHmmss",
126        "yyyyMMddHHmmssSSS",
127        "yyyy-MM-dd'T'HH:mm:ss",
128        "yyyy-MM-dd'T'HH:mm:ss.SSS",
129        "yyyy-MM-dd'T'HH:mm:ssXXX",
130        "yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
131        "EEE, dd MMM yyyy HH:mm:ss zzz",
132        "EEE MMM dd HH:mm:ss zzz yyyy",
133        "dd-MM-yyyy HH:mm:ss",
134        "dd/MM/yyyy HH:mm:ss",
135        "MM-dd-yyyy HH:mm:ss",
136        "MM/dd/yyyy HH:mm:ss",
137    };
138
139    /**
140     * DateTimeFormatter 缓存(Key: pattern)
141     */
142    private static final Map<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>(32);
143
144
145    /**
146     * JSON 序列化特性配置
147     */
148    private static final JSONWriter.Feature[] SERIAL_FEATURES = {
149        JSONWriter.Feature.WriteMapNullValue,
150    };
151
152
153    /**
154     * 通用类型转换入口
155     *
156     * @param value  源数据
157     * @param toType 目标类型(支持 Class / ParameterizedType)
158     * @param <T>    目标类型泛型
159     * @return 转换后的对象,失败时抛出 ConversionException
160     */
161    @SuppressWarnings("unchecked")
162    public static <T> T convert(Object value, Type toType) {
163        if (value == null) {
164            return null;
165        }
166
167        // 1. 目标类型是 Class 且已匹配,直接返回(零开销)
168        if (toType instanceof Class) {
169            Class<?> targetClass = (Class<?>) toType;
170            if (targetClass.isInstance(value)) {
171                return (T) value;
172            }
173            return (T) convertSimpleType(value, targetClass);
174        }
175
176        // 2. 泛型类型走 JSON 转换(兜底)
177        return (T) convertComplexType(value, toType);
178    }
179
180    /**
181     * 带默认值的转换:转换失败或结果为 null 时返回默认值
182     *
183     * @param value        源数据
184     * @param toType       目标类型
185     * @param defaultValue 默认值
186     * @param <T>          目标类型泛型
187     * @return 转换结果或默认值
188     */
189    @SuppressWarnings("unchecked")
190    public static <T> T convert(Object value, Type toType, T defaultValue) {
191        try {
192            T result = convert(value, toType);
193            return result != null ? result : defaultValue;
194        } catch (Exception e) {
195            return defaultValue;
196        }
197    }
198
199    /**
200     * 动态创建 ParameterizedType(用于运行时泛型)
201     *
202     * @param rawType  原始类型,如 List.class
203     * @param typeArgs 泛型参数,如 User.class
204     * @return ParameterizedType 实例
205     */
206    public static Type createParameterizedType(Class<?> rawType, Type... typeArgs) {
207        return new ParameterizedType() {
208            @Override
209            public Type[] getActualTypeArguments() {
210                return typeArgs.clone();
211            }
212
213            @Override
214            public Type getRawType() {
215                return rawType;
216            }
217
218            @Override
219            public Type getOwnerType() {
220                return null;
221            }
222        };
223    }
224
225    // ========== 简单类型转换(零 JSON 开销)==========
226
227    private static Object convertSimpleType(Object value, Class<?> targetType) {
228        // 处理基本类型 → 包装类型
229        if (targetType.isPrimitive()) {
230            targetType = PRIMITIVE_WRAPPER_MAP.getOrDefault(targetType, targetType);
231        }
232
233        // String: 直接 toString
234        if (targetType == String.class) {
235            return value.toString();
236        }
237
238        // 数值类型
239        if (NUMBER_TYPES.contains(targetType)) {
240            return convertNumber(value, targetType);
241        }
242
243        // Boolean
244        if (targetType == Boolean.class) {
245            return convertBoolean(value);
246        }
247
248        // Character
249        if (targetType == Character.class) {
250            return convertChar(value);
251        }
252
253        // Date / Time
254        if (targetType == Date.class) {
255            return convertToDate(value);
256        }
257        if (targetType == LocalDate.class) {
258            return convertToLocalDate(value);
259        }
260        if (targetType == LocalDateTime.class) {
261            return convertToLocalDateTime(value);
262        }
263        if (targetType == Instant.class) {
264            return convertToInstant(value);
265        }
266
267        // Enum
268        if (targetType.isEnum()) {
269            return convertToEnum(value, (Class<Enum>) targetType);
270        }
271
272        // 其他 Class 类型:兜底走 JSON
273        return convertByJson(value, targetType);
274    }
275
276    // ----- 数值转换 -----
277    private static Number convertNumber(Object value, Class<?> targetType) {
278        // 源是 Number:直接转换
279        if (value instanceof Number) {
280            Number num = (Number) value;
281            if (targetType == Byte.class) return num.byteValue();
282            if (targetType == Short.class) return num.shortValue();
283            if (targetType == Integer.class) return num.intValue();
284            if (targetType == Long.class) return num.longValue();
285            if (targetType == Float.class) return num.floatValue();
286            if (targetType == Double.class) return num.doubleValue();
287            if (targetType == BigDecimal.class) {
288                if (value instanceof BigDecimal) return (BigDecimal) value;
289                if (value instanceof Double || value instanceof Float) {
290                    return BigDecimal.valueOf(num.doubleValue());
291                }
292                return new BigDecimal(num.toString());
293            }
294            if (targetType == BigInteger.class) {
295                if (value instanceof BigInteger) return (BigInteger) value;
296                return BigInteger.valueOf(num.longValue());
297            }
298        }
299
300        // 源是 String:校验 + 解析
301        if (value instanceof String) {
302            String str = (String) value;
303            str = str.trim();
304            if (!isPureNumeric(str) && !isDecimalString(str)) {
305                throw new IllegalArgumentException("字符串 '" + str + "' 不是有效数值");
306            }
307            try {
308                if (targetType == Integer.class) return Integer.parseInt(str);
309                if (targetType == Long.class) return Long.parseLong(str);
310                if (targetType == Double.class) return Double.parseDouble(str);
311                if (targetType == Float.class) return Float.parseFloat(str);
312                if (targetType == BigDecimal.class) return new BigDecimal(str);
313                if (targetType == BigInteger.class) return new BigInteger(str);
314                if (targetType == Byte.class) return Byte.parseByte(str);
315                if (targetType == Short.class) return Short.parseShort(str);
316            } catch (NumberFormatException e) {
317                throw new IllegalArgumentException("无法将 '" + str + "' 转换为 " + targetType.getSimpleName(), e);
318            }
319        }
320
321        throw new IllegalArgumentException("无法将 " + value + " (" + value.getClass().getSimpleName() + ") 转换为 " + targetType);
322    }
323
324    /**
325     * 判断是否为纯整数字符串(可选符号 + 数字)
326     */
327    private static boolean isPureNumeric(String str) {
328        return PURE_INTEGER_PATTERN.matcher(str).matches();
329    }
330
331    /**
332     * 判断是否为小数字符串(包含小数点)
333     */
334    private static boolean isDecimalString(String str) {
335        if (str == null || str.isEmpty()) return false;
336        int dotCount = 0;
337        int start = (str.charAt(0) == '+' || str.charAt(0) == '-') ? 1 : 0;
338        for (int i = start; i < str.length(); i++) {
339            char c = str.charAt(i);
340            if (c == '.') {
341                if (++dotCount > 1) return false;
342            } else if (c < '0' || c > '9') {
343                return false;
344            }
345        }
346        return dotCount == 1;
347    }
348
349    // ----- Boolean 转换 -----
350    private static Boolean convertBoolean(Object value) {
351        if (value instanceof Boolean) return (Boolean) value;
352        if (value instanceof Number) {
353            Number num = (Number) value;
354            return num.intValue() != 0;
355        }
356        if (value instanceof String) {
357            String str = (String) value;
358            String s = str.trim().toLowerCase(Locale.ROOT);
359            if (FALSE_VALUES.contains(s)) return false;
360            if (TRUE_VALUES.contains(s)) return true;
361            // 非明确真/假值:非 null 即 true(可配置)
362            return value != null;
363        }
364        return value != null;
365    }
366
367    // ----- Character 转换 -----
368    private static Character convertChar(Object value) {
369        if (value instanceof Character) return (Character) value;
370        if (value instanceof String) {
371            String str = (String) value;
372            if (str.isEmpty()) {
373                throw new IllegalArgumentException("空字符串无法转换为 Character");
374            }
375            return str.charAt(0);
376        }
377        if (value instanceof Number) {
378            Number num = (Number) value;
379            return (char) num.intValue();
380        }
381        throw new IllegalArgumentException("无法将 " + value + " 转换为 Character");
382    }
383
384    // ----- Date 转换(多格式 + 缓存 + 时间戳优先)-----
385    private static Date convertToDate(Object value) {
386        if (value == null) return null;
387        if (value instanceof Date) return (Date) value;
388        if (value instanceof Number) {
389            Number num = (Number) value;
390            return new Date(num.longValue());
391        }
392        if (value instanceof String) {
393            String str = (String) value;
394            return parseDateFromString(str);
395        }
396        return parseDateFromString(value.toString());
397    }
398
399    private static Date parseDateFromString(String str) {
400        if (str == null || str.isEmpty()) {
401            throw new IllegalArgumentException("日期字符串不能为空");
402        }
403        str = str.trim();
404
405        // 策略1: 纯数字时间戳(10位秒 / 13位毫秒)
406        if (TIMESTAMP_PATTERN.matcher(str).matches()) {
407            long ts = Long.parseLong(str);
408            if (str.length() <= 10) ts *= 1000; // 秒 → 毫秒
409            return new Date(ts);
410        }
411
412        // 策略2: ISO_INSTANT 格式(带 Z / 时区)
413        try {
414            if (str.endsWith("Z") || str.contains("+") || (str.indexOf('-', 10) > 0 && !str.contains(" "))) {
415                Instant instant = Instant.parse(str);
416                return Date.from(instant);
417            }
418        } catch (DateTimeParseException ignore) {
419        }
420
421        // 策略3: 预定义格式轮询(缓存 Formatter)
422        for (String pattern : DATE_PATTERNS) {
423            try {
424                DateTimeFormatter formatter = getCachedFormatter(pattern);
425                TemporalAccessor accessor;
426                if (pattern.contains("HH") || pattern.contains("hh") || pattern.contains("H")) {
427                    accessor = LocalDateTime.parse(str, formatter);
428                    return Date.from(((LocalDateTime) accessor)
429                        .atZone(ZoneId.systemDefault())
430                        .toInstant());
431                } else {
432                    accessor = LocalDate.parse(str, formatter);
433                    return Date.from(((LocalDate) accessor)
434                        .atStartOfDay(ZoneId.systemDefault())
435                        .toInstant());
436                }
437            } catch (DateTimeParseException ignore) {
438                // 尝试下一个格式
439            }
440        }
441
442        // 策略4: 宽松解析兜底
443        Date fuzzy = tryFuzzyParse(str);
444        if (fuzzy != null) return fuzzy;
445
446        throw new IllegalArgumentException("无法解析日期: '" + str + "'");
447    }
448
449    private static DateTimeFormatter getCachedFormatter(String pattern) {
450        return FORMATTER_CACHE.computeIfAbsent(pattern, p ->
451            DateTimeFormatter.ofPattern(p)
452                .withLocale(Locale.CHINA)
453                .withZone(ZoneId.systemDefault())
454        );
455    }
456
457    private static Date tryFuzzyParse(String str) {
458        try {
459            // 移除中文、统一分隔符
460            String cleaned = str.replaceAll("[\\u4e00-\\u9fa5]", "")
461                .replaceAll("[./]", "-")
462                .trim();
463
464            // 尝试宽松格式:yyyy-M-d [H:m:s[.S]]
465            if (cleaned.matches("\\d{4}-\\d{1,2}-\\d{1,2}( \\d{1,2}:\\d{1,2}(:\\d{1,2}(\\.\\d{1,3})?)?)?")) {
466                DateTimeFormatter flexible = new DateTimeFormatterBuilder()
467                    .parseLenient()
468                    .appendPattern("yyyy-M-d")
469                    .optionalStart().appendLiteral(' ').appendPattern("H:m:s").optionalEnd()
470                    .optionalStart().appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true).optionalEnd()
471                    .toFormatter()
472                    .withZone(ZoneId.systemDefault());
473
474                if (cleaned.contains(":")) {
475                    LocalDateTime ldt = LocalDateTime.parse(cleaned, flexible);
476                    return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
477                } else {
478                    LocalDate ld = LocalDate.parse(cleaned, flexible);
479                    return Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant());
480                }
481            }
482
483            // 尝试 ISO_DATE_TIME
484            try {
485                LocalDateTime ldt = LocalDateTime.parse(str, DateTimeFormatter.ISO_DATE_TIME);
486                return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
487            } catch (DateTimeParseException ignore) {
488            }
489
490        } catch (Exception ignore) {
491            // 模糊解析失败
492        }
493        return null;
494    }
495
496    // ----- LocalDate 转换 -----
497    private static LocalDate convertToLocalDate(Object value) {
498        if (value == null) return null;
499        if (value instanceof LocalDate) return (LocalDate) value;
500        if (value instanceof Date) {
501            Date date = (Date) value;
502            return Instant.ofEpochMilli(date.getTime())
503                .atZone(ZoneId.systemDefault())
504                .toLocalDate();
505        }
506        if (value instanceof Long) {
507            long ts = (Long) value;
508            return Instant.ofEpochMilli(ts)
509                .atZone(ZoneId.systemDefault())
510                .toLocalDate();
511        }
512        if (value instanceof String) {
513            String str = (String) value;
514            // 优先尝试 ISO 格式
515            try {
516                return LocalDate.parse(str.trim(), DateTimeFormatter.ISO_DATE);
517            } catch (DateTimeParseException e) {
518                // 回退到多格式解析
519                Date date = parseDateFromString(str);
520                return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
521            }
522        }
523        throw new IllegalArgumentException("无法将 " + value + " 转换为 LocalDate");
524    }
525
526    // ----- LocalDateTime 转换 -----
527    private static LocalDateTime convertToLocalDateTime(Object value) {
528        if (value == null) return null;
529        if (value instanceof LocalDateTime) return (LocalDateTime) value;
530        if (value instanceof Date) {
531            Date date = (Date) value;
532            return Instant.ofEpochMilli(date.getTime())
533                .atZone(ZoneId.systemDefault())
534                .toLocalDateTime();
535        }
536        if (value instanceof Long) {
537            long ts = (Long) value;
538            return Instant.ofEpochMilli(ts)
539                .atZone(ZoneId.systemDefault())
540                .toLocalDateTime();
541        }
542        if (value instanceof String) {
543            String str = (String) value;
544            try {
545                return LocalDateTime.parse(str.trim(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
546            } catch (DateTimeParseException e) {
547                Date date = parseDateFromString(str);
548                return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
549            }
550        }
551        throw new IllegalArgumentException("无法将 " + value + " 转换为 LocalDateTime");
552    }
553
554    // ----- Instant 转换 -----
555    private static Instant convertToInstant(Object value) {
556        if (value == null) return null;
557        if (value instanceof Instant) return (Instant) value;
558        if (value instanceof Date) {
559            Date date = (Date) value;
560            return date.toInstant();
561        }
562        if (value instanceof Long) {
563            long ts = (Long) value;
564            return Instant.ofEpochMilli(ts);
565        }
566        if (value instanceof String) {
567            String str = (String) value;
568            try {
569                return Instant.parse(str.trim());
570            } catch (DateTimeParseException e) {
571                Date date = parseDateFromString(str);
572                return date.toInstant();
573            }
574        }
575        throw new IllegalArgumentException("无法将 " + value + " 转换为 Instant");
576    }
577
578    // ----- Enum 转换 -----
579    private static <E extends Enum<E>> E convertToEnum(Object value, Class<E> enumClass) {
580        if (enumClass.isInstance(value)) {
581            return (E) value;
582        }
583        if (value instanceof String) {
584            String name = (String) value;
585            return Enum.valueOf(enumClass, name.trim());
586        }
587        if (value instanceof Number) {
588            Number num = (Number) value;
589            E[] constants = enumClass.getEnumConstants();
590            int index = num.intValue();
591            if (index < 0 || index >= constants.length) {
592                throw new IllegalArgumentException("枚举索引 " + index + " 超出范围 [0, " + (constants.length - 1) + "]");
593            }
594            return constants[index];
595        }
596        throw new IllegalArgumentException("无法将 " + value + " 转换为枚举 " + enumClass.getSimpleName());
597    }
598
599    // ========== 复杂类型转换(JSON 兜底)==========
600
601    private static Object convertComplexType(Object value, Type toType) {
602        try {
603            if (value instanceof JSONArray) {
604                return ((JSONArray) value).to(toType);
605            }
606
607            if (value instanceof JSONObject) {
608                return ((JSONObject) value).to(toType);
609            }
610
611            // 优化:如果 value 已是 JSON 字符串,直接解析
612            if (value instanceof String && !(toType instanceof Class)) {
613                String jsonStr = (String) value;
614                return JSON.parseObject(jsonStr, toType);
615            }
616
617            String json = JSON.toJSONString(value, SERIAL_FEATURES);
618            return JSON.parseObject(json, toType);
619        } catch (RuntimeException e) {
620            throw new ConversionException("复杂类型转换失败: " + toType.getTypeName() + ", JSON 内容: " + JSON.toJSONString(value, SERIAL_FEATURES), e);
621        } catch (Error e) {
622            // Error 不应被捕获,直接抛出
623            throw e;
624        }
625    }
626
627    private static Object convertByJson(Object value, Class<?> targetType) {
628        try {
629            if (value instanceof JSONArray) {
630                return ((JSONArray) value).to(targetType);
631            }
632
633            if (value instanceof JSONObject) {
634                return ((JSONObject) value).to(targetType);
635            }
636            String json = JSON.toJSONString(value, SERIAL_FEATURES);
637            return JSON.parseObject(json, targetType);
638        } catch (RuntimeException e) {
639            throw new ConversionException("JSON 转换失败: " + targetType.getName() + ", JSON 内容: " + JSON.toJSONString(value, SERIAL_FEATURES), e);
640        } catch (Error e) {
641            throw e;
642        }
643    }
644
645
646    /**
647     * 类型转换异常
648     */
649    public static class ConversionException extends RuntimeException {
650        public ConversionException(String message, Throwable cause) {
651            super(message, cause);
652        }
653
654        public ConversionException(String message) {
655            super(message);
656        }
657    }
658
659}