ConvertOption.java

package space.sunqian.fs.object.convert;

import space.sunqian.annotation.Nonnull;
import space.sunqian.fs.Fs;
import space.sunqian.fs.base.chars.CharsKit;
import space.sunqian.fs.base.date.DateFormatter;
import space.sunqian.fs.base.number.NumFormatter;
import space.sunqian.fs.base.option.Option;
import space.sunqian.fs.base.option.OptionKit;
import space.sunqian.fs.base.string.NameMapper;
import space.sunqian.fs.collect.ArrayKit;
import space.sunqian.fs.io.IOOperator;
import space.sunqian.fs.object.builder.BuilderOperatorProvider;
import space.sunqian.fs.object.schema.MapSchemaParser;
import space.sunqian.fs.object.schema.ObjectSchemaParser;

import java.nio.charset.Charset;
import java.util.Map;

/**
 * Option for object conversion and data mapping.
 *
 * @author sunqian
 */
public enum ConvertOption {

    /**
     * Key of {@link #strictSourceTypeMode(boolean)}.
     * <p>
     * Option to enable strict source type mode. In strict type mode, the conversion will strictly treat the source
     * object as the specified source type.
     * <p>
     * By default, this option is disabled. In this case, if some error occurs when parsing the source type, the
     * conversion will try again with the {@link Object#getClass()} as the source type.
     */
    STRICT_SOURCE_TYPE_MODE,

    /**
     * Key of {@link #strictTargetTypeMode(boolean)}.
     * <p>
     * Option to enable strict target type mode. In strict type mode, the conversion will strictly convert the source
     * object to the target type, especially for target wildcard type and type variables.
     * <p>
     * By default, this option is disabled. In this case, the target type for wildcard and type variables will be
     * treated as their bounds type. For example, the target type of {@code ? extends String} will be treated as
     * {@code String}.
     */
    STRICT_TARGET_TYPE_MODE,

    /**
     * Key of {@link #newInstanceMode(boolean)}.
     * <p>
     * Option to enable new instance mode. In new instance mode, the conversion will always create a new instance of the
     * target type even if the target type is assignable from the source type.
     * <p>
     * By default, this option is disabled. In this case, the conversion could return the source object if the target
     * type is assignable from the source type.
     */
    NEW_INSTANCE_MODE,

    /**
     * Key of {@link #objectSchemaParser(ObjectSchemaParser)}.
     */
    OBJECT_SCHEMA_PARSER,

    /**
     * Key of {@link #mapSchemaParser(MapSchemaParser)}.
     */
    MAP_SCHEMA_PARSER,

    /**
     * Key of {@link #builderOperatorProvider(BuilderOperatorProvider)}.
     */
    BUILDER_OPERATOR_PROVIDER,

    /**
     * Key of {@link #objectCopier(ObjectCopier)}.
     */
    OBJECT_COPIER,

    /**
     * Key of {@link #nameMapper(NameMapper)}.
     */
    NAME_MAPPER,

    /**
     * Key of {@link #ignoreProperties(Object...)}.
     */
    IGNORE_PROPERTIES,

    /**
     * Key of {@link #ignoreNull(boolean)}.
     */
    IGNORE_NULL,

    /**
     * Key of {@link #includeClass(boolean)}.
     */
    INCLUDE_CLASS,

    /**
     * Key of {@link #ioOperator(IOOperator)}.
     */
    IO_OPERATOR,

    /**
     * Key of {@link #charset(Charset)}.
     */
    CHARSET,

    /**
     * Key of {@link #dateFormatter(DateFormatter)}.
     */
    DATE_FORMATTER,

    /**
     * Key of {@link #numFormatter(NumFormatter)}.
     */
    NUM_FORMATTER,
    ;

    /**
     * Sets option to enable strict source type mode. In strict type mode, the conversion will strictly treat the source
     * object as the specified source type.
     * <p>
     * By default, this option is disabled. In this case, if some error occurs when parsing the source type, the
     * conversion will try again with the {@link Object#getClass()} as the source type.
     *
     * @param strictSourceType whether to enable strict source type mode
     * @return an option to specify to enable/disable strict source type mode
     */
    public static @Nonnull Option<@Nonnull ConvertOption, ?> strictSourceTypeMode(boolean strictSourceType) {
        return Option.of(STRICT_SOURCE_TYPE_MODE, strictSourceType);
    }

    /**
     * Returns whether the given options specify to enable strict source type mode. The related option configuration
     * method is {@link #strictSourceTypeMode(boolean)}.
     *
     * @param options the given options
     * @return {@code true} if the given options specify to enable strict source type mode, {@code false} otherwise
     */
    public static boolean isStrictSourceTypeMode(@Nonnull Option<?, ?> @Nonnull [] options) {
        return OptionKit.isEnabled(ConvertOption.STRICT_SOURCE_TYPE_MODE, options);
    }

    /**
     * Sets option to enable new instance mode. In new instance mode, the conversion will always create a new instance
     * of the target type even if the target type is assignable from the source type.
     * <p>
     * By default, this option is disabled. In this case, the conversion could return the source object if the target
     * type is assignable from the source type.
     *
     * @param strictTargetType whether to enable strict target type mode
     * @return an option to specify to enable/disable strict target type mode
     */
    public static @Nonnull Option<@Nonnull ConvertOption, ?> strictTargetTypeMode(boolean strictTargetType) {
        return Option.of(STRICT_TARGET_TYPE_MODE, strictTargetType);
    }

    /**
     * Returns whether the given options specify to enable strict target type mode. The related option configuration
     * method is {@link #strictTargetTypeMode(boolean)}.
     *
     * @param options the given options
     * @return {@code true} if the given options specify to enable strict target type mode, {@code false} otherwise
     */
    public static boolean isStrictTargetTypeMode(@Nonnull Option<?, ?> @Nonnull [] options) {
        return OptionKit.isEnabled(ConvertOption.STRICT_TARGET_TYPE_MODE, options);
    }

    /**
     * Sets option to enable new instance mode. In new instance mode, the conversion will always create a new instance
     * of the target type even if the target type is assignable from the source type.
     * <p>
     * By default, this option is disabled. In this case, the conversion could return the source object if the target
     * type is assignable from the source type.
     *
     * @param newInstanceMode whether to enable new instance mode
     * @return an option to specify to enable/disable new instance mode
     */
    public static @Nonnull Option<@Nonnull ConvertOption, ?> newInstanceMode(boolean newInstanceMode) {
        return Option.of(NEW_INSTANCE_MODE, newInstanceMode);
    }

    /**
     * Returns whether the given options specify to enable new instance mode. The related option configuration method is
     * {@link #newInstanceMode(boolean)}.
     *
     * @param options the given options
     * @return {@code true} if the given options specify to enable new instance mode, {@code false} otherwise
     */
    public static boolean isNewInstanceMode(@Nonnull Option<?, ?> @Nonnull [] options) {
        return OptionKit.isEnabled(ConvertOption.NEW_INSTANCE_MODE, options);
    }

    /**
     * Returns an option to specify the object schema parser.
     * <p>
     * By default, {@link ObjectSchemaParser#defaultCachedParser()} is used.
     *
     * @param schemaParser the specified object schema parser
     * @return an option to specify the object schema parser
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull ObjectSchemaParser> objectSchemaParser(
        @Nonnull ObjectSchemaParser schemaParser
    ) {
        return Option.of(OBJECT_SCHEMA_PARSER, schemaParser);
    }

    /**
     * Returns the specified {@link ObjectSchemaParser} from the given options, or
     * {@link ObjectSchemaParser#defaultCachedParser()} if the given options does not contain a
     * {@link ConvertOption#OBJECT_SCHEMA_PARSER}.
     *
     * @param options the given options
     * @return the specified {@link ObjectSchemaParser} from the given options, or
     * {@link ObjectSchemaParser#defaultCachedParser()} if the given options does not contain a
     * {@link ConvertOption#OBJECT_SCHEMA_PARSER}
     */
    public static @Nonnull ObjectSchemaParser getObjectSchemaParser(@Nonnull Option<?, ?> @Nonnull ... options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.OBJECT_SCHEMA_PARSER, options),
            ObjectSchemaParser.defaultCachedParser()
        );
    }

    /**
     * Returns an option to specify the map schema parser.
     * <p>
     * By default, {@link MapSchemaParser#defaultCachedParser()} is used.
     *
     * @param schemaParser the specified map schema parser
     * @return an option to specify the map schema parser
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull MapSchemaParser> mapSchemaParser(
        @Nonnull MapSchemaParser schemaParser
    ) {
        return Option.of(MAP_SCHEMA_PARSER, schemaParser);
    }

    /**
     * Returns the specified {@link MapSchemaParser} from the given options, or
     * {@link MapSchemaParser#defaultCachedParser()} if the given options does not contain a
     * {@link ConvertOption#MAP_SCHEMA_PARSER}.
     *
     * @param options the given options
     * @return the specified {@link MapSchemaParser} from the given options, or
     * {@link MapSchemaParser#defaultCachedParser()} if the given options does not contain a
     * {@link ConvertOption#MAP_SCHEMA_PARSER}
     */
    public static @Nonnull MapSchemaParser getMapSchemaParser(@Nonnull Option<?, ?> @Nonnull ... options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.MAP_SCHEMA_PARSER, options),
            MapSchemaParser.defaultCachedParser()
        );
    }

    /**
     * Returns an option to specify the {@link BuilderOperatorProvider} to generate data object during the conversion.
     * <p>
     * By default, the {@link BuilderOperatorProvider#defaultCachedProvider()} is used.
     *
     * @param operatorProvider the {@link BuilderOperatorProvider} to be specified
     * @return an option to specify the {@link BuilderOperatorProvider} to generate data object during the conversion
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull BuilderOperatorProvider> builderOperatorProvider(
        @Nonnull BuilderOperatorProvider operatorProvider
    ) {
        return Option.of(BUILDER_OPERATOR_PROVIDER, operatorProvider);
    }

    /**
     * Returns the specified {@link BuilderOperatorProvider} from the given options, or
     * {@link BuilderOperatorProvider#defaultCachedProvider()} if the given options does not contain a
     * {@link ConvertOption#BUILDER_OPERATOR_PROVIDER}.
     *
     * @param options the given options
     * @return the specified {@link BuilderOperatorProvider} from the given options, or
     * {@link BuilderOperatorProvider#defaultCachedProvider()} if the given options does not contain a
     * {@link ConvertOption#BUILDER_OPERATOR_PROVIDER}
     */
    public static @Nonnull BuilderOperatorProvider getBuilderOperatorProvider(
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.BUILDER_OPERATOR_PROVIDER, options),
            BuilderOperatorProvider.defaultCachedProvider()
        );
    }

    /**
     * Returns an option to specify the {@link ObjectCopier}.
     * <p>
     * By default, the {@link ObjectCopier#defaultCopier()} is used.
     *
     * @param objectCopier the {@link ObjectCopier} to be specified
     * @return an option to specify the {@link ObjectCopier}
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull ObjectCopier> objectCopier(
        @Nonnull ObjectCopier objectCopier
    ) {
        return Option.of(OBJECT_COPIER, objectCopier);
    }

    /**
     * Returns the specified {@link ObjectCopier} from the given options, or {@link ObjectCopier#defaultCopier()} if the
     * given options does not contain a {@link ConvertOption#OBJECT_COPIER}.
     *
     * @param options the given options
     * @return the specified {@link ObjectCopier} from the given options, or {@link ObjectCopier#defaultCopier()} if the
     * given options does not contain a {@link ConvertOption#OBJECT_COPIER}
     */
    public static @Nonnull ObjectCopier getObjectCopier(@Nonnull Option<?, ?> @Nonnull ... options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.OBJECT_COPIER, options),
            ObjectCopier.defaultCopier()
        );
    }

    /**
     * Returns an option to specify the {@link NameMapper}.
     * <p>
     * Note that this configuration is only valid for the {@link String} type names, mainly for the property names (both
     * source and target), and executed before the configured {@link ObjectCopier.Handler} (if any, and it means the
     * property name received by the property mapper will be mapped by the name mapper first). For the property names
     * whose type is not {@link String}, such as non-{@link String} keys of a {@link Map}, this configuration will not
     * take effect.
     * <p>
     * By default, this option is disabled.
     *
     * @param nameMapper the {@link NameMapper} to be specified
     * @return an option to specify the {@link NameMapper}
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull NameMapper> nameMapper(
        @Nonnull NameMapper nameMapper
    ) {
        return Option.of(NAME_MAPPER, nameMapper);
    }

    /**
     * Returns the {@link NameMapper}specified by the options. The related option {@link #nameMapper(NameMapper)}.
     *
     * @param options the options to check
     * @return the {@link NameMapper} specified by the options, or {@link NameMapper#keep()} if not specified
     */
    public static @Nonnull NameMapper getNameMapper(@Nonnull Option<?, ?> @Nonnull [] options) {
        NameMapper mapper = OptionKit.findValue(ConvertOption.NAME_MAPPER, options);
        return mapper != null ? mapper : NameMapper.keep();
    }

    /**
     * Returns an option to ignore copy properties with the specified property names (or key for map).
     * <p>
     * By default, all readable properties will be attempted to be copied.
     *
     * @param ignoredProperties the names (or key) of ignored properties
     * @return an option to ignore copy properties with the specified property names (or key for map)
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull Object @Nonnull []> ignoreProperties(
        @Nonnull Object @Nonnull ... ignoredProperties
    ) {
        return Option.of(IGNORE_PROPERTIES, ignoredProperties);
    }

    /**
     * Check whether the property is specified to ignore by the given options. The related option
     * {@link #ignoreProperties(Object...)}.
     *
     * @param propertyName the property name to check
     * @param options      the given options
     * @return {@code true} if the property is specified to ignore, {@code false} otherwise
     */
    public static boolean isIgnoreProperty(@Nonnull Object propertyName, @Nonnull Option<?, ?> @Nonnull [] options) {
        Object[] ignoredProperties = OptionKit.findValue(ConvertOption.IGNORE_PROPERTIES, options);
        if (ignoredProperties == null) {
            return false;
        }
        return ArrayKit.indexOf(ignoredProperties, propertyName) >= 0;
    }

    /**
     * Returns an option to specify whether ignores {@code null} properties from the source object in the conversion.
     * <p>
     * By default, this option is disabled.
     *
     * @param ignoreNull whether ignores {@code null} properties from the source object
     * @return an option to specify to ignore null properties from the source object in the conversion
     */
    public static @Nonnull Option<@Nonnull ConvertOption, ?> ignoreNull(boolean ignoreNull) {
        return Option.of(IGNORE_NULL, ignoreNull);
    }

    /**
     * Check whether the given option specifies to enable to ignore null values. The related option
     * {@link #ignoreNull(boolean)}.
     *
     * @param options the given options to check
     * @return {@code true} if the option specifies to enable to ignore null values, {@code false} otherwise
     */
    public static boolean isIgnoreNull(@Nonnull Option<?, ?> @Nonnull [] options) {
        return OptionKit.isEnabled(ConvertOption.IGNORE_NULL, options);
    }

    /**
     * Returns an option to specify whether includes {@code class} properties, which from {@link Object#getClass()}, for
     * the source object in the conversion.
     * <p>
     * By default, this option is disabled.
     *
     * @param includeClass whether includes {@code class} properties, which from {@link Object#getClass()}, for the
     *                     source object
     * @return an option to specify to include {@code class} properties, which from {@link Object#getClass()}, for the
     * source object in the conversion
     */
    public static @Nonnull Option<@Nonnull ConvertOption, ?> includeClass(boolean includeClass) {
        return Option.of(INCLUDE_CLASS, includeClass);
    }

    /**
     * Check whether the given option specifies to enable to include {@code class} properties, which from
     * {@link Object#getClass()}. The related option configuration method is {@link #includeClass(boolean)}.
     *
     * @param options the given options to check
     * @return {@code true} if the option specifies to enable to include {@code class} properties, {@code false}
     * otherwise
     */
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public static boolean isIncludeClass(@Nonnull Option<?, ?> @Nonnull [] options) {
        return OptionKit.isEnabled(ConvertOption.INCLUDE_CLASS, options);
    }

    /**
     * Returns an option to specify the {@link IOOperator} if needed.
     * <p>
     * By default, the {@link IOOperator#defaultOperator()} is used.
     *
     * @param ioOperator the {@link IOOperator} to be specified
     * @return an option to specify the {@link IOOperator} if needed
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull IOOperator> ioOperator(
        @Nonnull IOOperator ioOperator
    ) {
        return Option.of(IO_OPERATOR, ioOperator);
    }

    /**
     * Returns the specified {@link IOOperator} from the given options, or {@link IOOperator#defaultOperator()} if the
     * given options does not contain a {@link ConvertOption#IO_OPERATOR}.
     *
     * @param options the given options
     * @return the specified {@link IOOperator} from the given options, or {@link IOOperator#defaultOperator()} if the
     * given options does not contain a {@link ConvertOption#IO_OPERATOR}
     */
    public static @Nonnull IOOperator getIOOperator(@Nonnull Option<?, ?> @Nonnull [] options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.IO_OPERATOR, options),
            IOOperator.defaultOperator()
        );
    }

    /**
     * Returns an option to specify the {@link Charset} if needed.
     * <p>
     * By default, the {@link CharsKit#defaultCharset()} is used.
     *
     * @param charset the {@link Charset} to be specified
     * @return an option to specify the {@link Charset} if needed
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull Charset> charset(
        @Nonnull Charset charset
    ) {
        return Option.of(CHARSET, charset);
    }

    /**
     * Returns the specified {@link Charset} from the given options, or {@link CharsKit#defaultCharset()} if the given
     * options does not contain a {@link ConvertOption#CHARSET}.
     *
     * @param options the given options
     * @return the specified {@link Charset} from the given options, or {@link CharsKit#defaultCharset()} if the given
     * options does not contain a {@link ConvertOption#CHARSET}
     */
    public static @Nonnull Charset getCharset(@Nonnull Option<?, ?> @Nonnull [] options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.CHARSET, options),
            CharsKit.defaultCharset()
        );
    }

    /**
     * Returns an option to specify the {@link DateFormatter} if needed.
     * <p>
     * By default, the {@link DateFormatter#defaultFormatter()} is used.
     *
     * @param dateFormatter the {@link DateFormatter} to be specified
     * @return an option to specify the {@link DateFormatter} if needed
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull DateFormatter> dateFormatter(
        @Nonnull DateFormatter dateFormatter
    ) {
        return Option.of(DATE_FORMATTER, dateFormatter);
    }

    /**
     * Returns the specified {@link DateFormatter} from the given options, or {@link DateFormatter#defaultFormatter()}
     * if the given options does not contain a {@link ConvertOption#DATE_FORMATTER}.
     *
     * @param options the given options
     * @return the specified {@link DateFormatter} from the given options, or {@link DateFormatter#defaultFormatter()}
     * if the given options does not contain a {@link ConvertOption#DATE_FORMATTER}
     */
    public static @Nonnull DateFormatter getDateFormatter(@Nonnull Option<?, ?> @Nonnull [] options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.DATE_FORMATTER, options),
            DateFormatter.defaultFormatter()
        );
    }

    /**
     * Returns an option to specify the {@link NumFormatter} if needed.
     * <p>
     * By default, no {@link NumFormatter} is used.
     *
     * @param numFormatter the {@link NumFormatter} to be specified
     * @return an option to specify the {@link NumFormatter} if needed
     */
    public static @Nonnull Option<@Nonnull ConvertOption, @Nonnull NumFormatter> numFormatter(
        @Nonnull NumFormatter numFormatter
    ) {
        return Option.of(NUM_FORMATTER, numFormatter);
    }

    /**
     * Returns the specified {@link NumFormatter} from the given options, or {@link NumFormatter#common()} if the given
     * options does not contain a {@link ConvertOption#NUM_FORMATTER}.
     *
     * @param options the given options
     * @return the specified {@link NumFormatter} from the given options, or {@link NumFormatter#common()} if the given
     * options does not contain a {@link ConvertOption#NUM_FORMATTER}
     */
    public static @Nonnull NumFormatter getNumFormatter(@Nonnull Option<?, ?> @Nonnull [] options) {
        return Fs.nonnull(
            OptionKit.findValue(ConvertOption.NUM_FORMATTER, options),
            NumFormatter.common()
        );
    }
}