ObjectCopierImpl.java

package space.sunqian.fs.object.convert;

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.base.option.OptionKit;
import space.sunqian.fs.collect.ListKit;
import space.sunqian.fs.object.convert.handlers.CommonCopierHandler;
import space.sunqian.fs.object.schema.DataSchemaException;
import space.sunqian.fs.object.schema.MapSchema;
import space.sunqian.fs.object.schema.MapSchemaParser;
import space.sunqian.fs.object.schema.ObjectProperty;
import space.sunqian.fs.object.schema.ObjectSchema;
import space.sunqian.fs.object.schema.ObjectSchemaParser;

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

final class ObjectCopierImpl implements ObjectCopier, ObjectCopier.Handler {

    static final @Nonnull ObjectCopier DEFAULT = new ObjectCopierImpl(
        ListKit.list(CommonCopierHandler.getInstance()),
        Collections.emptyList()
    );

    private final @Nonnull List<@Nonnull Handler> handlers;
    private final @Nonnull List<@Nonnull Option<?, ?>> defaultOptions;
    private final @Nonnull Option<?, ?> @Nonnull [] defaultOptionsArray;

    ObjectCopierImpl(
        @Nonnull @RetainedParam List<@Nonnull Handler> handlers,
        @Nonnull @RetainedParam List<@Nonnull Option<?, ?>> defaultOptions
    ) {
        this.handlers = Collections.unmodifiableList(handlers);
        this.defaultOptions = Collections.unmodifiableList(defaultOptions);
        this.defaultOptionsArray = defaultOptions.toArray(new Option[0]);
    }

    @Override
    public void copyProperties(
        @Nonnull Object src,
        @Nonnull Type srcType,
        @Nonnull Object dst,
        @Nonnull Type dstType,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) throws ObjectCopyException {
        try {
            @Nonnull Option<?, ?> @Nonnull [] actualOptions = OptionKit.mergeOptions(defaultOptionsArray, options);
            if (src instanceof Map) {
                MapSchemaParser srcParser = ConvertOption.getMapSchemaParser(actualOptions);
                MapSchema srcSchema = parseMapSchema((Map<?, ?>) src, srcParser, srcType, actualOptions);
                if (dst instanceof Map) {
                    MapSchema dstParser = parseMapSchema((Map<?, ?>) dst, srcParser, dstType, actualOptions);
                    mapToMap(Fs.as(src), srcSchema, Fs.as(dst), dstParser, converter, actualOptions);
                } else {
                    ObjectSchemaParser objectParser = ConvertOption.getObjectSchemaParser(actualOptions);
                    ObjectSchema dstSchema = parseObjectSchema(dst, objectParser, dstType, actualOptions);
                    mapToObject(Fs.as(src), srcSchema, dst, dstSchema, converter, actualOptions);
                }
            } else {
                ObjectSchemaParser srcParser = ConvertOption.getObjectSchemaParser(actualOptions);
                ObjectSchema srcSchema = parseObjectSchema(src, srcParser, srcType, actualOptions);
                if (dst instanceof Map) {
                    MapSchemaParser dstParser = ConvertOption.getMapSchemaParser(actualOptions);
                    MapSchema dstSchema = parseMapSchema((Map<?, ?>) dst, dstParser, dstType, actualOptions);
                    objectToMap(src, srcSchema, Fs.as(dst), dstSchema, converter, actualOptions);
                } else {
                    ObjectSchema dstSchema = srcParser.parse(dstType);
                    objectToObject(src, srcSchema, dst, dstSchema, converter, actualOptions);
                }
            }
        } catch (ObjectCopyException e) {
            throw e;
        } catch (Exception e) {
            throw new ObjectCopyException(e);
        }
    }

    private @Nonnull ObjectSchema parseObjectSchema(
        @Nonnull Object object,
        @Nonnull ObjectSchemaParser parser,
        @Nonnull Type type,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        try {
            return parser.parse(type);
        } catch (DataSchemaException e) {
            if (ConvertOption.isStrictSourceTypeMode(options)) {
                throw e;
            }
            Type objType = object.getClass();
            if (Objects.equals(objType, type)) {
                throw e;
            }
            return parser.parse(objType);
        }
    }

    private @Nonnull MapSchema parseMapSchema(
        @Nonnull Map<?, ?> object,
        @Nonnull MapSchemaParser parser,
        @Nonnull Type type,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        try {
            return parser.parse(type);
        } catch (DataSchemaException e) {
            if (ConvertOption.isStrictSourceTypeMode(options)) {
                throw e;
            }
            /*
            this never happen:
              Type objType = object.getClass();
              if (Objects.equals(objType, type)) {
                  throw e;
              }
             */
            return parser.parse(object.getClass());
        }
    }

    @Override
    public @Nonnull List<@Nonnull Handler> handlers() {
        return handlers;
    }

    @Override
    public @Nonnull List<@Nonnull Option<?, ?>> defaultOptions() {
        return defaultOptions;
    }

    @Override
    public @Nonnull Handler asHandler() {
        return this;
    }

    void mapToMap(
        @Nonnull Map<Object, Object> src,
        @Nonnull MapSchema srcSchema,
        @Nonnull Map<Object, Object> dst,
        @Nonnull MapSchema dstSchema,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        src.forEach((srcKey, srcValue) -> {
            try {
                for (Handler handler : handlers) {
                    boolean goon = handler.copyProperty(
                        srcKey, srcValue, src, srcSchema, dst, dstSchema, converter, options
                    );
                    if (!goon) {
                        break;
                    }
                }
            } catch (Exception e) {
                throw new ObjectCopyException(e);
            }
        });
    }

    void mapToObject(
        @Nonnull Map<Object, Object> src,
        @Nonnull MapSchema srcSchema,
        @Nonnull Object dst,
        @Nonnull ObjectSchema dstSchema,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        src.forEach((srcKey, srcValue) -> {
            try {
                for (Handler handler : handlers) {
                    boolean goon = handler.copyProperty(
                        srcKey, srcValue, src, srcSchema, dst, dstSchema, converter, options
                    );
                    if (!goon) {
                        break;
                    }
                }
            } catch (Exception e) {
                throw new ObjectCopyException(e);
            }
        });
    }

    void objectToMap(
        @Nonnull Object src,
        @Nonnull ObjectSchema srcSchema,
        @Nonnull Map<Object, Object> dst,
        @Nonnull MapSchema dstSchema,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        srcSchema.properties().forEach((srcPropertyName, srcProperty) -> {
            try {
                for (Handler handler : handlers) {
                    boolean goon = handler.copyProperty(
                        srcProperty.name(), srcProperty, src, srcSchema, dst, dstSchema, converter, options
                    );
                    if (!goon) {
                        break;
                    }
                }
            } catch (Exception e) {
                throw new ObjectCopyException(e);
            }
        });
    }

    void objectToObject(
        @Nonnull Object src,
        @Nonnull ObjectSchema srcSchema,
        @Nonnull Object dst,
        @Nonnull ObjectSchema dstSchema,
        @Nonnull ObjectConverter converter,
        @Nonnull Option<?, ?> @Nonnull ... options
    ) {
        srcSchema.properties().forEach((srcPropertyName, srcProperty) -> {
            try {
                for (Handler handler : handlers) {
                    boolean goon = handler.copyProperty(
                        srcProperty.name(), srcProperty, src, srcSchema, dst, dstSchema, converter, options
                    );
                    if (!goon) {
                        break;
                    }
                }
            } catch (Exception e) {
                throw new ObjectCopyException(e);
            }
        });
    }

    @Override
    public boolean copyProperty(@Nonnull Object srcKey, @Nullable Object srcValue, @Nonnull Map<Object, Object> src, @Nonnull MapSchema srcSchema, @Nonnull Map<Object, Object> dst, @Nonnull MapSchema dstSchema, @Nonnull ObjectConverter converter, @Nonnull Option<?, ?> @Nonnull ... options) throws Exception {
        boolean goon = true;
        for (Handler handler : handlers) {
            goon = handler.copyProperty(
                srcKey, srcValue, src, srcSchema, dst, dstSchema, converter, options
            );
            if (!goon) {
                break;
            }
        }
        return goon;
    }

    @Override
    public boolean copyProperty(@Nonnull Object srcKey, @Nullable Object srcValue, @Nonnull Map<Object, Object> src, @Nonnull MapSchema srcSchema, @Nonnull Object dst, @Nonnull ObjectSchema dstSchema, @Nonnull ObjectConverter converter, @Nonnull Option<?, ?> @Nonnull ... options) throws Exception {
        boolean goon = true;
        for (Handler handler : handlers) {
            goon = handler.copyProperty(
                srcKey, srcValue, src, srcSchema, dst, dstSchema, converter, options
            );
            if (!goon) {
                break;
            }
        }
        return goon;
    }

    @Override
    public boolean copyProperty(@Nonnull String srcPropertyName, @Nonnull ObjectProperty srcProperty, @Nonnull Object src, @Nonnull ObjectSchema srcSchema, @Nonnull Map<Object, Object> dst, @Nonnull MapSchema dstSchema, @Nonnull ObjectConverter converter, @Nonnull Option<?, ?> @Nonnull ... options) throws Exception {
        boolean goon = true;
        for (Handler handler : handlers) {
            goon = handler.copyProperty(
                srcPropertyName, srcProperty, src, srcSchema, dst, dstSchema, converter, options
            );
            if (!goon) {
                break;
            }
        }
        return goon;
    }

    @Override
    public boolean copyProperty(@Nonnull String srcPropertyName, @Nonnull ObjectProperty srcProperty, @Nonnull Object src, @Nonnull ObjectSchema srcSchema, @Nonnull Object dst, @Nonnull ObjectSchema dstSchema, @Nonnull ObjectConverter converter, @Nonnull Option<?, ?> @Nonnull ... options) throws Exception {
        boolean goon = true;
        for (Handler handler : handlers) {
            goon = handler.copyProperty(
                srcPropertyName, srcProperty, src, srcSchema, dst, dstSchema, converter, options
            );
            if (!goon) {
                break;
            }
        }
        return goon;
    }
}