CommonConvertHandler.java

package space.sunqian.common.object.convert.handlers;

import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.Fs;
import space.sunqian.common.base.chars.CharsKit;
import space.sunqian.common.base.date.DateFormatter;
import space.sunqian.common.base.enums.EnumKit;
import space.sunqian.common.base.number.NumberKit;
import space.sunqian.common.base.option.Option;
import space.sunqian.common.collect.ArrayKit;
import space.sunqian.common.collect.ArrayOperator;
import space.sunqian.common.collect.CollectKit;
import space.sunqian.common.io.BufferKit;
import space.sunqian.common.io.IOOperator;
import space.sunqian.common.object.convert.ConvertOption;
import space.sunqian.common.object.convert.DataMapper;
import space.sunqian.common.object.convert.ObjectConverter;
import space.sunqian.common.object.data.ObjectBuilder;
import space.sunqian.common.object.data.ObjectBuilderProvider;
import space.sunqian.common.reflect.ClassKit;
import space.sunqian.common.reflect.ReflectionException;
import space.sunqian.common.reflect.TypeKit;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.IntFunction;

/**
 * The common implementation of {@link ObjectConverter.Handler}, also be the default last handler of
 * {@link ObjectConverter#defaultConverter()}.
 * <p>
 * This handler providers the common conversion logic for all types. This is a table showing the conversion logic of
 * this handler for different target types:
 * <table summary="Conversion Logic">
 * <tr>
 *     <th>Target</th>
 *     <th>Source</th>
 *     <th>Conversion Logic</th>
 * </tr>
 * <tr>
 *     <td rowspan="5">{@link String}, {@link CharSequence}</td>
 *     <td>{@link InputStream}, {@link ReadableByteChannel}, {@link Reader}, {@link ByteBuffer}, {@code byte[]}</td>
 *     <td>Using {@link ConvertOption#ioOperator(IOOperator)} and {@link ConvertOption#charset(Charset)}
 *     to decode to string.</td>
 * </tr>
 * <tr>
 *     <td>{@code char[]}</td>
 *     <td>Using {@link String#String(char[])} to construct to string.</td>
 * </tr>
 * <tr>
 *     <td>{@link BigDecimal}</td>
 *     <td>Using {@link BigDecimal#toPlainString()}.</td>
 * </tr>
 * <tr>
 *     <td>Date and Time Objects</td>
 *     <td>Using {@link ConvertOption#timeFormatter(DateFormatter)} to handle.</td>
 * </tr>
 * <tr>
 *     <td>Others</td>
 *     <td>Using {@link Object#toString()}.</td>
 * </tr>
 * <tr>
 *     <td>{@code byte[]}, {@code char[]}, {@link ByteBuffer}, {@link CharBuffer}</td>
 *     <td>{@link String}</td>
 *     <td>Using {@link String#getBytes(Charset)} or {@link String#toCharArray()}.</td>
 * </tr>
 * <tr>
 *     <td rowspan="2">Numbers</td>
 *     <td>{@link String}</td>
 *     <td>Using {@link NumberKit#toNumber(CharSequence, Class)}.</td>
 * </tr>
 * <tr>
 *     <td>Other Numbers</td>
 *     <td>Using {@link NumberKit#toNumber(Number, Class)}.</td>
 * </tr>
 * <tr>
 *     <td>{@code long} and {@link Long}</td>
 *     <td>Date and Time</td>
 *     <td>Returns epoch milliseconds of the date or time object.</td>
 * </tr>
 * <tr>
 *     <td rowspan="2">{@code boolean} and {@link Boolean}</td>
 *     <td>Numbers</td>
 *     <td>{@code false} for {@code 0}, otherwise {@code true}.</td>
 * </tr>
 * <tr>
 *     <td>{@link String}</td>
 *     <td>{@code true} for {@code equalsIgnoreCase("true")}, otherwise {@code false}.</td>
 * </tr>
 * <tr>
 *     <td rowspan="2">Date and Time</td>
 *     <td>{@link String} and Other Date Time Objects</td>
 *     <td>Using {@link ConvertOption#timeFormatter(DateFormatter)} to handle.</td>
 * </tr>
 * <tr>
 *     <td>{@code long} and {@link Long}</td>
 *     <td>Treated as an epoch milliseconds, then using {@link ConvertOption#timeFormatter(DateFormatter)} to
 *     handle.</td>
 * </tr>
 * <tr>
 *     <td>Enums</td>
 *     <td>Any Objects</td>
 *     <td>Using {@link EnumKit#findEnum(Class, String)} with the name from {@link Object#toString()}.</td>
 * </tr>
 * <tr>
 *     <td>Array and Collection Objects</td>
 *     <td>Array or Iterable Objects</td>
 *     <td>Array created using reflection. Collection created using its constructor, the supported collection types:
 *     {@link Iterable}, {@link Collection}, {@link List}, {@link AbstractList}, {@link ArrayList}, {@link LinkedList},
 *     {@link CopyOnWriteArrayList}, {@link Set}, {@link LinkedHashSet}, {@link HashSet}, {@link TreeSet},
 *     {@link ConcurrentSkipListSet}. After creating the container, uses the {@code converter} parameter to handle
 *     component types.
 *     </td>
 * </tr>
 * <tr>
 *     <td>Map and Data Objects</td>
 *     <td>Any Objects</td>
 *     <td>Generating data object is based on {@link ConvertOption#builderProvider(ObjectBuilderProvider)} and
 *     {@link ConvertOption#dataMapper(DataMapper)}. Generating map using its constructor, and copying properties
 *     also using {@link ConvertOption#dataMapper(DataMapper)}. The supported map types:
 *     {@link Map}, {@link AbstractMap}, {@link LinkedHashMap}, {@link HashMap}, {@link TreeMap}, {@link ConcurrentMap},
 *     {@link ConcurrentHashMap}, {@link Hashtable}, {@link ConcurrentSkipListMap}.
 *     </td>
 * </tr>
 * </table>
 * <p>
 * Note that this handler typically creates new objects and does not perform the same handing as
 * {@link AssignableConvertHandler}.
 */
public class CommonConvertHandler implements ObjectConverter.Handler {

    @Override
    public Object convert(
        @Nullable Object src,
        @Nonnull Type srcType,
        @Nonnull Type target,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws Exception {
        if (src == null) {
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
        if (target instanceof Class<?>) {
            Class<?> targetClass = (Class<?>) target;
            if (targetClass.isEnum()) {
                // to enum:
                String name = src.toString();
                return EnumKit.findEnum(Fs.as(targetClass), name);
            }
            if (srcType.equals(String.class)) {
                if (target.equals(byte[].class) || target.equals(ByteBuffer.class)) {
                    Charset charset = Fs.nonnull(
                        Option.findValue(ConvertOption.CHARSET, options),
                        CharsKit.defaultCharset()
                    );
                    byte[] bytes = ((String) src).getBytes(charset);
                    return target.equals(ByteBuffer.class) ? ByteBuffer.wrap(bytes) : bytes;
                }
                if (target.equals(char[].class)) {
                    return ((String) src).toCharArray();
                }
                if (target.equals(CharBuffer.class)) {
                    return CharBuffer.wrap((String) src);
                }
            }
            if (targetClass.isArray()) {
                // to array
                return toArray(src, srcType, targetClass, converter, options);
            }
            ClassHandler classHandler = TargetClasses.get(targetClass);
            if (classHandler != null) {
                return classHandler.convert(src, srcType, targetClass, converter, options);
            }
            IntFunction<Collection<Object>> collectionFunc = CollectionClasses.get(targetClass);
            if (collectionFunc != null) {
                // to collection
                return toCollection(
                    src, srcType, collectionFunc, targetClass.getTypeParameters()[0], converter, options
                );
            }
            // to map or data object
            return toDataObject(src, srcType, targetClass, target, converter, options);
        } else if (target instanceof GenericArrayType) {
            // to generic array
            return toArray(src, srcType, (GenericArrayType) target, converter, options);
        } else if (target instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) target;
            Class<?> rawTarget = (Class<?>) paramType.getRawType();
            IntFunction<Collection<Object>> collectionFunc = CollectionClasses.get(rawTarget);
            if (collectionFunc != null) {
                // to collection
                return toCollection(
                    src, srcType, collectionFunc, paramType.getActualTypeArguments()[0], converter, options
                );
            }
            // to map or data object
            return toDataObject(src, srcType, rawTarget, target, converter, options);
        }
        return ObjectConverter.Status.HANDLER_CONTINUE;
    }

    private Object toArray(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull Class<?> target,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        return toArray(
            src,
            srcType,
            target,
            target.getComponentType(),
            converter,
            options
        );
    }

    private Object toArray(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull GenericArrayType target,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        Class<?> targetClass = Fs.asNonnull(TypeKit.toRuntimeClass(target));
        return toArray(
            src,
            srcType,
            targetClass,
            target.getGenericComponentType(),
            converter,
            options
        );
    }

    private Object toArray(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull Class<?> target,
        @Nonnull Type targetComponentType,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        if (srcType instanceof Class<?>) {
            Class<?> srcClass = (Class<?>) srcType;
            if (srcClass.isArray()) {
                ArrayOperator srcOperator = ArrayOperator.of(srcClass);
                int size = srcOperator.size(src);
                Object newArray = ArrayKit.newArray(target.getComponentType(), size);
                Class<?> srcComponentType = srcClass.getComponentType();
                ArrayOperator targetOperator = ArrayOperator.of(target);
                for (int i = 0; i < size; i++) {
                    Object srcElement = srcOperator.get(src, i);
                    Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
                    targetOperator.set(newArray, i, targetElement);
                }
                return newArray;
            }
        }
        Type srcComponentType = resolveComponentType(srcType);
        if (srcComponentType == null) {
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
        Collection<?> srcCollection;
        if (src instanceof Collection<?>) {
            srcCollection = (Collection<?>) src;
        } else if (src instanceof Iterable<?>) {
            Iterable<?> iter = (Iterable<?>) src;
            srcCollection = CollectKit.addAll(new ArrayList<>(), iter);
        } else {
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
        int size = srcCollection.size();
        Object newArray = ArrayKit.newArray(target.getComponentType(), size);
        ArrayOperator targetOperator = ArrayOperator.of(target);
        int i = 0;
        for (Object srcElement : srcCollection) {
            Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
            targetOperator.set(newArray, i++, targetElement);
        }
        return newArray;
    }

    private Object toCollection(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull IntFunction<Collection<Object>> collectionFunc,
        @Nonnull Type targetComponentType,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        if (srcType instanceof Class<?>) {
            Class<?> srcClass = (Class<?>) srcType;
            if (srcClass.isArray()) {
                ArrayOperator srcOperator = ArrayOperator.of(srcClass);
                int size = srcOperator.size(src);
                Collection<Object> newCollection = collectionFunc.apply(size);
                Class<?> srcComponentType = srcClass.getComponentType();
                for (int i = 0; i < size; i++) {
                    Object srcElement = srcOperator.get(src, i);
                    Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
                    newCollection.add(targetElement);
                }
                return newCollection;
            }
        }
        Type srcComponentType = resolveComponentType(srcType);
        if (srcComponentType == null) {
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
        Collection<?> srcCollection;
        if (src instanceof Collection<?>) {
            srcCollection = (Collection<?>) src;
        } else if (src instanceof Iterable<?>) {
            Iterable<?> iter = (Iterable<?>) src;
            srcCollection = CollectKit.addAll(new ArrayList<>(), iter);
        } else {
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
        int size = srcCollection.size();
        Collection<Object> newCollection = collectionFunc.apply(size);
        int i = 0;
        for (Object srcElement : srcCollection) {
            Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
            newCollection.add(targetElement);
        }
        return newCollection;
    }

    private @Nullable Type resolveComponentType(@Nonnull Type type) {
        try {
            return TypeKit.resolveActualTypeArguments(type, Iterable.class).get(0);
        } catch (ReflectionException e) {
            return null;
        }
    }

    private Object toDataObject(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull Class<?> rawTarget,
        @Nonnull Type target,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws Exception {
        IntFunction<Object> mapFunc = MapClasses.get(rawTarget);
        DataMapper dataMapper = Fs.nonnull(
            Option.findValue(ConvertOption.DATA_MAPPER, options),
            DataMapper.defaultMapper()
        );
        if (mapFunc != null) {
            Object targetObject = mapFunc.apply(0);
            dataMapper.copyProperties(src, srcType, targetObject, target, converter, options);
            return targetObject;
        } else {
            ObjectBuilderProvider builderProvider = Fs.nonnull(
                Option.findValue(ConvertOption.BUILDER_PROVIDER, options),
                ObjectBuilderProvider.defaultProvider()
            );
            ObjectBuilder builder = builderProvider.builder(target);
            if (builder == null) {
                return ObjectConverter.Status.HANDLER_CONTINUE;
            }
            Object targetBuilder = builder.newBuilder();
            dataMapper.copyProperties(src, srcType, targetBuilder, builder.builderType(), converter, options);
            return builder.build(targetBuilder);
        }
    }

    private static final class CollectionClasses {

        private static final @Nonnull Map<@Nonnull Type, @Nonnull IntFunction<@Nonnull Collection<Object>>> CLASS_MAP;

        static {
            CLASS_MAP = new HashMap<>();
            CLASS_MAP.put(Iterable.class, ArrayList::new);
            CLASS_MAP.put(Collection.class, HashSet::new);
            CLASS_MAP.put(List.class, ArrayList::new);
            CLASS_MAP.put(AbstractList.class, ArrayList::new);
            CLASS_MAP.put(ArrayList.class, ArrayList::new);
            CLASS_MAP.put(LinkedList.class, size -> new LinkedList<>());
            CLASS_MAP.put(CopyOnWriteArrayList.class, size -> new CopyOnWriteArrayList<>());
            CLASS_MAP.put(Set.class, HashSet::new);
            CLASS_MAP.put(LinkedHashSet.class, LinkedHashSet::new);
            CLASS_MAP.put(HashSet.class, HashSet::new);
            CLASS_MAP.put(TreeSet.class, size -> new TreeSet<>());
            CLASS_MAP.put(ConcurrentSkipListSet.class, size -> new ConcurrentSkipListSet<>());
        }

        public static @Nullable IntFunction<@Nonnull Collection<Object>> get(@Nonnull Class<?> target) {
            return CLASS_MAP.get(target);
        }
    }

    private static final class MapClasses {

        private static final @Nonnull Map<@Nonnull Type, @Nonnull IntFunction<@Nonnull Object>> CLASS_MAP;

        static {
            CLASS_MAP = new HashMap<>();
            CLASS_MAP.put(Map.class, HashMap::new);
            CLASS_MAP.put(AbstractMap.class, HashMap::new);
            CLASS_MAP.put(LinkedHashMap.class, LinkedHashMap::new);
            CLASS_MAP.put(HashMap.class, HashMap::new);
            CLASS_MAP.put(TreeMap.class, size -> new TreeMap<>());
            CLASS_MAP.put(ConcurrentMap.class, ConcurrentHashMap::new);
            CLASS_MAP.put(ConcurrentHashMap.class, ConcurrentHashMap::new);
            CLASS_MAP.put(Hashtable.class, Hashtable::new);
            CLASS_MAP.put(ConcurrentSkipListMap.class, size -> new ConcurrentSkipListMap<>());
        }

        public static @Nullable IntFunction<@Nonnull Object> get(@Nonnull Class<?> target) {
            return CLASS_MAP.get(target);
        }
    }

    private interface ClassHandler {
        Object convert(
            @Nonnull Object src,
            @Nonnull Type srcType,
            @Nonnull Class<?> target,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) throws Exception;
    }

    private static final class TargetClasses {

        private static final @Nonnull Map<@Nonnull Type, @Nonnull ClassHandler> HANDLER_MAP;

        static {
            HANDLER_MAP = new HashMap<>();
            HANDLER_MAP.put(String.class, StringClassHandler.INST);
            HANDLER_MAP.put(CharSequence.class, StringClassHandler.INST);
            HANDLER_MAP.put(boolean.class, BooleanClassHandler.INST);
            HANDLER_MAP.put(Boolean.class, BooleanClassHandler.INST);
            HANDLER_MAP.put(byte.class, NumberClassHandler.INST);
            HANDLER_MAP.put(short.class, NumberClassHandler.INST);
            HANDLER_MAP.put(char.class, NumberClassHandler.INST);
            HANDLER_MAP.put(int.class, NumberClassHandler.INST);
            HANDLER_MAP.put(long.class, NumberClassHandler.INST);
            HANDLER_MAP.put(float.class, NumberClassHandler.INST);
            HANDLER_MAP.put(double.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Byte.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Short.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Character.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Integer.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Long.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Float.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Double.class, NumberClassHandler.INST);
            HANDLER_MAP.put(BigInteger.class, NumberClassHandler.INST);
            HANDLER_MAP.put(BigDecimal.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Number.class, NumberClassHandler.INST);
            HANDLER_MAP.put(Date.class, TimeClassHandler.INST);
            HANDLER_MAP.put(Instant.class, TimeClassHandler.INST);
            HANDLER_MAP.put(LocalDateTime.class, TimeClassHandler.INST);
            HANDLER_MAP.put(ZonedDateTime.class, TimeClassHandler.INST);
            HANDLER_MAP.put(OffsetDateTime.class, TimeClassHandler.INST);
            HANDLER_MAP.put(LocalDate.class, TimeClassHandler.INST);
            HANDLER_MAP.put(LocalTime.class, TimeClassHandler.INST);
        }

        public static @Nullable CommonConvertHandler.ClassHandler get(@Nonnull Class<?> target) {
            return HANDLER_MAP.get(target);
        }
    }

    private enum StringClassHandler implements ClassHandler {

        INST;

        @Override
        public Object convert(
            @Nonnull Object src,
            @Nonnull Type srcType,
            @Nonnull Class<?> target,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) throws Exception {
            if (src instanceof char[]) {
                return new String((char[]) src);
            }
            if (src instanceof BigDecimal) {
                return ((BigDecimal) src).toPlainString();
            }
            if (src instanceof Date) {
                DateFormatter dateFormatter = Fs.nonnull(
                    Option.findValue(ConvertOption.TIME_FORMATTER, options),
                    DateFormatter.defaultFormatter()
                );
                return dateFormatter.format((Date) src);
            }
            if (src instanceof TemporalAccessor) {
                DateFormatter dateFormatter = Fs.nonnull(
                    Option.findValue(ConvertOption.TIME_FORMATTER, options),
                    DateFormatter.defaultFormatter()
                );
                return dateFormatter.format((TemporalAccessor) src);
            }
            Charset charset = Fs.nonnull(
                Option.findValue(ConvertOption.CHARSET, options),
                CharsKit.defaultCharset()
            );
            if (src instanceof byte[]) {
                return new String((byte[]) src, charset);
            }
            if (src instanceof ByteBuffer) {
                return BufferKit.string((ByteBuffer) src, charset);
            }
            IOOperator ioOperator = Fs.nonnull(
                Option.findValue(ConvertOption.IO_OPERATOR, options),
                IOOperator.defaultOperator()
            );
            if (src instanceof Reader) {
                return ioOperator.string((Reader) src);
            }
            if (src instanceof InputStream) {
                return ioOperator.string((InputStream) src, charset);
            }
            if (src instanceof ReadableByteChannel) {
                return ioOperator.string((ReadableByteChannel) src, charset);
            }
            return src.toString();
        }
    }

    private enum NumberClassHandler implements ClassHandler {

        INST;

        @Override
        public Object convert(
            @Nonnull Object src,
            @Nonnull Type srcType,
            @Nonnull Class<?> target,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) {
            if (src instanceof String) {
                return NumberKit.toNumber((String) src, target);
            }
            if (!(srcType instanceof Class<?>)) {
                return ObjectConverter.Status.HANDLER_CONTINUE;
            }
            if (target.equals(Long.class) || target.equals(long.class)) {
                if (srcType.equals(Date.class)) {
                    return ((Date) src).getTime();
                }
                if (TemporalAccessor.class.isAssignableFrom((Class<?>) srcType)) {
                    TemporalAccessor ta = (TemporalAccessor) src;
                    DateFormatter dateFormatter = Fs.nonnull(
                        Option.findValue(ConvertOption.TIME_FORMATTER, options),
                        DateFormatter.defaultFormatter()
                    );
                    Date date = dateFormatter.convert(ta, Date.class);
                    return date.getTime();
                }
            }
            if (Number.class.isAssignableFrom(ClassKit.wrapperClass((Class<?>) srcType))) {
                Number srcNum = (Number) src;
                return NumberKit.toNumber(srcNum, target);
            }
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
    }

    private enum BooleanClassHandler implements ClassHandler {

        INST;

        @Override
        public Object convert(
            @Nonnull Object src,
            @Nonnull Type srcType,
            @Nonnull Class<?> target,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) {
            if (src instanceof Boolean) {
                return src;
            }
            if (src instanceof Number) {
                return ((Number) src).intValue() != 0;
            }
            if (src instanceof String) {
                return "true".equalsIgnoreCase((String) src);
            }
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
    }

    private enum TimeClassHandler implements ClassHandler {

        INST;

        @Override
        public Object convert(
            @Nonnull Object src,
            @Nonnull Type srcType,
            @Nonnull Class<?> target,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) {
            DateFormatter dateFormatter = Fs.nonnull(
                Option.findValue(ConvertOption.TIME_FORMATTER, options),
                DateFormatter.defaultFormatter()
            );
            if (srcType.equals(String.class)) {
                return dateFormatter.parse((String) src, target);
            }
            if (srcType.equals(Date.class)) {
                return dateFormatter.convert((Date) src, target);
            }
            if (srcType.equals(long.class) || srcType.equals(Long.class)) {
                Date date = new Date((Long) src);
                return dateFormatter.convert(date, target);
            }
            if (srcType instanceof Class<?>) {
                if (TemporalAccessor.class.isAssignableFrom((Class<?>) srcType)) {
                    return dateFormatter.convert((TemporalAccessor) src, target);
                }
            }
            return ObjectConverter.Status.HANDLER_CONTINUE;
        }
    }
}