ObjectConverter.java

package space.sunqian.fs.object.convert;

import space.sunqian.annotation.Immutable;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.annotation.RetainedParam;
import space.sunqian.fs.Fs;
import space.sunqian.fs.base.option.Option;
import space.sunqian.fs.collect.ListKit;
import space.sunqian.fs.object.builder.BuilderOperatorProvider;
import space.sunqian.fs.object.convert.handlers.AssignableConvertHandler;
import space.sunqian.fs.object.convert.handlers.CommonConvertHandler;
import space.sunqian.fs.reflect.TypeRef;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;

/**
 * This interface is used to convert an object from the specified type to the target type.
 * <p>
 * It contains and uses a list of {@link Handler}s to sequentially attempt conversion. A handler can return
 * {@link Status#HANDLER_CONTINUE}, {@link Status#HANDLER_BREAK} or a normal value as the final result. The conversion
 * logic is as follows:
 * <pre>{@code
 * for (Handler handler : handlers()) {
 *     Object ret;
 *     try {
 *         ret = handler.convert(src, srcType, target, this, options);
 *     } catch (Exception e) {
 *         throw new ObjectConvertException(e);
 *     }
 *     if (ret == Status.HANDLER_CONTINUE) {
 *         continue;
 *     }
 *     if (ret == Status.HANDLER_BREAK) {
 *         throw new UnsupportedObjectConvertException(src, srcType, target, this, options);
 *     }
 *     return ret;
 * }
 * throw new UnsupportedObjectConvertException(src, srcType, target, this, options);
 * }</pre>
 * <p>
 * A converter can have default options. The actual options used in a conversion are merged with the default options and
 * the additional options of the conversion method. Note the default options of which keys are same as the additional
 * options will be overridden by those additional options.
 * <p>
 * The thread safety of the methods in this interface is determined by its dependent option objects, such as
 * {@link BuilderOperatorProvider}, {@link ObjectCopier}, and other objects. By default, they are all thread-safe.
 *
 * @author sunqian
 * @implNote The default implementations of {@link ObjectConverter} support annotations defined in
 * {@link space.sunqian.fs.object.annotation}.
 */
public interface ObjectConverter {

    /**
     * Returns the default {@link ObjectConverter}. Here are handlers in the default converter:
     * <ul>
     *     <li>{@link AssignableConvertHandler#getInstance()};</li>
     *     <li>{@link CommonConvertHandler#getInstance()};</li>
     * </ul>
     *
     * @return the default converter
     * @see AssignableConvertHandler
     * @see CommonConvertHandler
     */
    static @Nonnull ObjectConverter defaultConverter() {
        return ObjectConverterImpl.DEFAULT;
    }

    /**
     * Creates and returns a new {@link ObjectConverter} with the given handlers.
     *
     * @param handlers the given handlers
     * @return a new {@link ObjectConverter} with the given handlers
     */
    static @Nonnull ObjectConverter newConverter(@Nonnull @RetainedParam Handler @Nonnull ... handlers) {
        return newConverter(ListKit.list(handlers));
    }

    /**
     * Creates and returns a new {@link ObjectConverter} with given handlers.
     *
     * @param handlers given handlers
     * @return a new {@link ObjectConverter} with given handlers
     */
    static @Nonnull ObjectConverter newConverter(@Nonnull @RetainedParam List<@Nonnull Handler> handlers) {
        return newConverter(handlers, Collections.emptyList());
    }

    /**
     * Creates and returns a new {@link ObjectConverter} with given handlers and default options.
     *
     * @param handlers       given handlers
     * @param defaultOptions given default options
     * @return a new {@link ObjectConverter} with given handlers and default options
     */
    static @Nonnull ObjectConverter newConverter(
        @Nonnull @RetainedParam List<@Nonnull Handler> handlers,
        @Nonnull @RetainedParam List<@Nonnull Option<?, ?>> defaultOptions
    ) {
        return new ObjectConverterImpl(handlers, defaultOptions);
    }

    /**
     * Converts the given source object to the target type.
     * <p>
     * The additional options parameter can be empty, in which case the default options will be used. Otherwise,
     * conversion will use the options merged from the default options and additional options. Note the default options
     * of which keys are same as the additional options will be overridden by those additional options.
     *
     * @param src        the given source object
     * @param targetType the target type
     * @param options    the additional conversion options
     * @param <T>        the target type
     * @return the converted object, {@code null} is permitted
     * @throws UnsupportedObjectConvertException if the conversion to the target type is unsupported
     * @throws ObjectConvertException            if the conversion failed
     */
    default <T> T convert(
        @Nullable Object src,
        @Nonnull Class<? extends T> targetType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws UnsupportedObjectConvertException, ObjectConvertException {
        return Fs.as(convert(src, (Type) targetType, options));
    }

    /**
     * Converts the given source object from the specified source type to the target type.
     * <p>
     * The additional options parameter can be empty, in which case the default options will be used. Otherwise,
     * conversion will use the options merged from the default options and additional options. Note the default options
     * of which keys are same as the additional options will be overridden by those additional options.
     *
     * @param src        the given source object
     * @param srcType    the specified source type
     * @param targetType the target type
     * @param options    the additional conversion options
     * @param <T>        the target type
     * @return the converted object, {@code null} is permitted
     * @throws UnsupportedObjectConvertException if the conversion from the specified source type to the target type is
     *                                           unsupported
     * @throws ObjectConvertException            if the conversion failed
     */
    default <T> T convert(
        @Nullable Object src,
        @Nonnull Type srcType,
        @Nonnull Class<? extends T> targetType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws UnsupportedObjectConvertException, ObjectConvertException {
        return Fs.as(convert(src, srcType, (Type) targetType, options));
    }

    /**
     * Converts the given source object to the target type.
     * <p>
     * The additional options parameter can be empty, in which case the default options will be used. Otherwise,
     * conversion will use the options merged from the default options and additional options. Note the default options
     * of which keys are same as the additional options will be overridden by those additional options.
     *
     * @param src        the given source object
     * @param targetType the type ref of the target type
     * @param options    the additional conversion options
     * @param <T>        the target type
     * @return the converted object, {@code null} is permitted
     * @throws UnsupportedObjectConvertException if the conversion to the target type is unsupported
     * @throws ObjectConvertException            if the conversion failed
     */
    default <T> T convert(
        @Nullable Object src,
        @Nonnull TypeRef<? extends T> targetType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws UnsupportedObjectConvertException, ObjectConvertException {
        return Fs.as(convert(src, targetType.type(), options));
    }

    /**
     * Converts the given source object from the specified source type to the target type.
     * <p>
     * The additional options parameter can be empty, in which case the default options will be used. Otherwise,
     * conversion will use the options merged from the default options and additional options. Note the default options
     * of which keys are same as the additional options will be overridden by those additional options.
     *
     * @param src        the given source object
     * @param srcType    the specified source type
     * @param targetType the type ref of the target type
     * @param options    the additional conversion options
     * @param <T>        the target type
     * @return the converted object, {@code null} is permitted
     * @throws UnsupportedObjectConvertException if the conversion from the specified source type to the target type is
     *                                           unsupported
     * @throws ObjectConvertException            if the conversion failed
     */
    default <T> T convert(
        @Nullable Object src,
        @Nonnull Type srcType,
        @Nonnull TypeRef<? extends T> targetType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws UnsupportedObjectConvertException, ObjectConvertException {
        return Fs.as(convert(src, srcType, targetType.type(), options));
    }

    /**
     * Converts the given source object to the target type.
     * <p>
     * The additional options parameter can be empty, in which case the default options will be used. Otherwise,
     * conversion will use the options merged from the default options and additional options. Note the default options
     * of which keys are same as the additional options will be overridden by those additional options.
     *
     * @param src        the given source object
     * @param targetType the target type
     * @param options    the additional conversion options
     * @return the converted object, {@code null} is permitted
     * @throws UnsupportedObjectConvertException if the conversion to the target type is unsupported
     * @throws ObjectConvertException            if the conversion failed
     */
    default Object convert(
        @Nullable Object src,
        @Nonnull Type targetType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws UnsupportedObjectConvertException, ObjectConvertException {
        return convert(src, src == null ? Object.class : src.getClass(), targetType, options);
    }

    /**
     * Converts the given source object from the specified source type to the target type.
     * <p>
     * The additional options parameter can be empty, in which case the default options will be used. Otherwise,
     * conversion will use the options merged from the default options and additional options. Note the default options
     * of which keys are same as the additional options will be overridden by those additional options.
     *
     * @param src        the given source object
     * @param srcType    the specified source type
     * @param targetType the target type
     * @param options    the additional conversion options
     * @return the converted object, {@code null} is permitted
     * @throws UnsupportedObjectConvertException if the conversion from the specified source type to the target type is
     *                                           unsupported
     * @throws ObjectConvertException            if the conversion failed
     */
    Object convert(
        @Nullable Object src,
        @Nonnull Type srcType,
        @Nonnull Type targetType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws UnsupportedObjectConvertException, ObjectConvertException;

    /**
     * Returns all handlers of this {@link ObjectConverter}.
     *
     * @return all handlers of this {@link ObjectConverter}
     */
    @Nonnull
    @Immutable
    List<@Nonnull Handler> handlers();

    /**
     * Returns the default options of this {@link ObjectConverter}.
     *
     * @return the default options of this {@link ObjectConverter}
     */
    @Nonnull
    @Immutable
    List<@Nonnull Option<?, ?>> defaultOptions();

    /**
     * Returns a new {@link ObjectConverter} of which first handler is the given handler and the next handler is this
     * {@link ObjectConverter} as a {@link Handler}. This method is equivalent:
     * <pre>{@code
     * newConverter(firstHandler, this.asHandler())
     * }</pre>
     *
     * @param firstHandler the first handler
     * @return a new {@link ObjectConverter} of which first handler is the given handler and the next handler is this
     * {@link ObjectConverter} as a {@link Handler}
     */
    default @Nonnull ObjectConverter withFirstHandler(@Nonnull Handler firstHandler) {
        return newConverter(firstHandler, this.asHandler());
    }

    /**
     * Returns a new {@link ObjectConverter} of which default options are the given options.
     *
     * @param defaultOptions the default options
     * @return a new {@link ObjectConverter} of which default options are the given options
     */
    default @Nonnull ObjectConverter withDefaultOptions(@Nonnull Option<?, ?> @Nonnull ... defaultOptions) {
        return newConverter(handlers(), ListKit.list(defaultOptions));
    }

    /**
     * Returns this {@link ObjectConverter} as a {@link Handler}.
     *
     * @return this {@link ObjectConverter} as a {@link Handler}
     */
    @Nonnull
    Handler asHandler();

    /**
     * Handler for {@link ObjectConverter}, provides the specific conversion logic.
     * <p>
     * The thread safety of the methods in this interface is determined by its dependent {@link ObjectCopier},
     * {@link BuilderOperatorProvider}, and other objects. By default, they are all thread-safe.
     *
     * @author sunqian
     */
    interface Handler {

        /**
         * Converts the given source object to the given target type.
         *
         * @param src        the given source object
         * @param srcType    the specified type of the given source object
         * @param targetType the specified type of the target object
         * @param converter  the converter where this handler in
         * @param options    the other conversion options
         * @return the converted object, {@code null} is permitted, or {@link Status#HANDLER_CONTINUE} /
         * {@link Status#HANDLER_BREAK} if conversion failed
         * @throws Exception any exception can be thrown here
         */
        Object convert(
            @Nullable Object src,
            @Nonnull Type srcType,
            @Nonnull Type targetType,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) throws Exception;
    }

    /**
     * Represents the status of conversion.
     *
     * @author sunqian
     */
    enum Status {

        /**
         * This status is returned by a {@link Handler} in a conversion process, to indicate that the current handler
         * cannot convert but can continue to convert by the next handler.
         */
        HANDLER_CONTINUE,

        /**
         * This status is returned by a {@link Handler} in a conversion process, to indicate that the current handler
         * cannot convert and the conversion process needs to end in failure.
         */
        HANDLER_BREAK
    }
}