DataMapper.java

package space.sunqian.common.object.convert;

import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.base.option.Option;
import space.sunqian.common.object.data.DataSchema;
import space.sunqian.common.object.data.MapSchema;
import space.sunqian.common.object.data.ObjectSchema;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * This interface is used to map data properties from an object to another object. The object should be a {@link Map} or
 * a non-map object which can be parsed to {@link MapSchema} and {@link ObjectSchema}.
 * <p>
 * A {@link DataMapper} typically uses a {@link SchemaCache} to cache the parsed {@link DataSchema}s, and the thread
 * safety is determined by the {@link SchemaCache}. By default, they are thread-safe.
 *
 * @author sunqian
 */
public interface DataMapper {

    /**
     * Returns the default data mapper.
     * <p>
     * The default data mapper will cache the {@link DataSchema}s parsed into a {@link ConcurrentHashMap} if needed, so
     * it is thread-safe.
     *
     * @return the default data mapper
     */
    static @Nonnull DataMapper defaultMapper() {
        return DataMapperImpl.DEFAULT;
    }

    /**
     * Returns a new data mapper with the given schema cache. The thread safety is determined by the given cache.
     *
     * @param schemaCache the given schema cache
     * @return a new data mapper with the given schema cache
     */
    static @Nonnull DataMapper newMapper(@Nonnull SchemaCache schemaCache) {
        return new DataMapperImpl(schemaCache);
    }

    /**
     * Returns a new data mapper with the given map as schema cache. The thread safety is determined by the given map.
     *
     * @param map the given map as schema cache
     * @return a new data mapper with the given map as schema cache
     */
    static @Nonnull DataMapper newMapper(@Nonnull Map<@Nonnull Type, @Nonnull DataSchema> map) {
        return newMapper(newSchemaCache(map));
    }

    /**
     * Returns a new data schema cache with the given map. The thread safety is determined by the given map.
     *
     * @param map the given map
     * @return a new data schema cache with the given map
     */
    static @Nonnull SchemaCache newSchemaCache(@Nonnull Map<@Nonnull Type, @Nonnull DataSchema> map) {
        return new DataMapperImpl.SchemaCacheImpl(map);
    }

    /**
     * Copy properties from the given source object to the given destination object. The object can be a {@link Map} or
     * a non-map object which can be parsed to {@link ObjectSchema}.
     * <p>
     * The options parameter can be empty, in which case the default behavior will be used, or built-in options in
     * {@link ConvertOption} or other custom options for custom implementations.
     *
     * @param src     the given source object
     * @param dst     the given destination object
     * @param options the options for copying properties
     * @throws ObjectConvertException if an error occurs during copying properties
     */
    default void copyProperties(
        @Nonnull Object src, @Nonnull Object dst, @Nonnull Option<?, ?> @Nonnull ... options
    ) throws ObjectConvertException {
        copyProperties(src, src.getClass(), dst, dst.getClass(), options);
    }

    /**
     * Copy properties from the given source object to the given destination object. The object can be a {@link Map} or
     * a non-map object which can be parsed to {@link ObjectSchema}.
     * <p>
     * The options parameter can be empty, in which case the default behavior will be used, or built-in options in
     * {@link ConvertOption} or other custom options for custom implementations.
     *
     * @param src       the given source object
     * @param dst       the given destination object
     * @param converter the converter for converting values of the properties if needed
     * @param options   the options for copying properties
     * @throws ObjectConvertException if an error occurs during copying properties
     */
    default void copyProperties(
        @Nonnull Object src,
        @Nonnull Object dst,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws ObjectConvertException {
        copyProperties(src, src.getClass(), dst, dst.getClass(), converter, options);
    }

    /**
     * Copy properties from the given source object to the given destination object. The object can be a {@link Map} or
     * a non-map object which can be parsed to {@link ObjectSchema}.
     * <p>
     * The options parameter can be empty, in which case the default behavior will be used, or built-in options in
     * {@link ConvertOption} or other custom options for custom implementations.
     *
     * @param src     the given source object
     * @param srcType specifies the type of the given source object
     * @param dst     the given destination object
     * @param dstType specifies the type of the given destination object
     * @param options the options for copying properties
     * @throws ObjectConvertException if an error occurs during copying properties
     */
    default void copyProperties(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull Object dst,
        @Nonnull Type dstType,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws ObjectConvertException {
        copyProperties(
            src,
            srcType,
            dst,
            dstType,
            ObjectConverter.defaultConverter(),
            options
        );
    }

    /**
     * Copy properties from the given source object to the given destination object. The object can be a {@link Map} or
     * a non-map object which can be parsed to {@link ObjectSchema}.
     * <p>
     * The options parameter can be empty, in which case the default behavior will be used, or built-in options in
     * {@link ConvertOption} or other custom options for custom implementations.
     *
     * @param src       the given source object
     * @param srcType   specifies the type of the given source object
     * @param dst       the given destination object
     * @param dstType   specifies the type of the given destination object
     * @param converter the converter for converting values of the properties if needed
     * @param options   the options for copying properties
     * @throws ObjectConvertException if an error occurs during copying properties
     */
    void copyProperties(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull Object dst,
        @Nonnull Type dstType,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws ObjectConvertException;

    /**
     * Cache for {@link DataSchema}s parsed during the mapping process.
     */
    interface SchemaCache {

        /**
         * Returns the {@link DataSchema} for the given type. If the schema is not cached, it will be loaded by the
         * given loader. The semantics of this method are the same as {@link Map#computeIfAbsent(Object, Function)}.
         *
         * @param type   the given type to be parsed to {@link DataSchema}
         * @param loader the loader for loading new {@link DataSchema}
         * @return the {@link DataSchema} for the given type
         * @throws ObjectConvertException if an error occurs during parsing
         */
        @Nonnull
        DataSchema get(
            @Nonnull Type type,
            @Nonnull Function<? super @Nonnull Type, ? extends @Nonnull DataSchema> loader
        ) throws ObjectConvertException;
    }

    /**
     * Property mapper for copying object property, this interface is called when copying each property.
     */
    interface PropertyMapper {

        /**
         * Maps the source property with the specified name. this method determines the name and value of the actual
         * destination property that the specified property needs to be copied to. The returned entry's key and value
         * are the name and value of the actual destination property. If this method returns {@code null}, then the
         * specified property will not be copied.
         * <p>
         * This method is applicable to both {@link Map} and non-map object. For non-map objects, the type of property
         * name must be {@link String}.
         *
         * @param propertyName the name of the specified property to be copied
         * @param src          the source object
         * @param srcSchema    the schema of the source object
         * @param dst          the destination object
         * @param dstSchema    the schema of the destination object
         * @param converter    the converter used in the mapping process
         * @param options      the options used in the mapping process
         * @return the mapped name and value, may be {@code null} to ignore copy of this property
         */
        Map.@Nullable Entry<@Nonnull Object, Object> map(
            @Nonnull Object propertyName,
            @Nonnull Object src,
            @Nonnull DataSchema srcSchema,
            @Nonnull Object dst,
            @Nonnull DataSchema dstSchema,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        );
    }

    /**
     * Exception handler for copying object property, used to handle exceptions thrown when copying a property.
     */
    interface ExceptionHandler {

        /**
         * Handles the exception thrown when copying a source property to a destination property, can throw an exception
         * directly here.
         * <p>
         * This method is applicable to both {@link Map} and non-map object. For non-map objects, the type of property
         * name must be {@link String}.
         *
         * @param e            the exception thrown when copying a source property to a destination property
         * @param propertyName the name of the specified property to be copied
         * @param src          the source object
         * @param srcSchema    the schema of the source object
         * @param dst          the destination object
         * @param dstSchema    the schema of the destination object
         * @param converter    the converter used in the mapping process
         * @param options      the options used in the mapping process
         * @throws Exception any exception can be thrown here
         */
        void handle(
            @Nonnull Throwable e,
            @Nonnull Object propertyName,
            @Nonnull Object src,
            @Nonnull DataSchema srcSchema,
            @Nonnull Object dst,
            @Nonnull DataSchema dstSchema,
            @Nonnull ObjectConverter converter,
            @Nonnull Option<?, ?> @Nonnull ... options
        ) throws Exception;
    }
}