DataMapperImpl.java
package space.sunqian.common.object.convert;
import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.common.Fs;
import space.sunqian.common.base.option.Option;
import space.sunqian.common.collect.ArrayKit;
import space.sunqian.common.object.data.DataSchema;
import space.sunqian.common.object.data.MapSchema;
import space.sunqian.common.object.data.MapSchemaParser;
import space.sunqian.common.object.data.ObjectProperty;
import space.sunqian.common.object.data.ObjectSchema;
import space.sunqian.common.object.data.ObjectSchemaParser;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
final class DataMapperImpl implements DataMapper {
static final @Nonnull DataMapper DEFAULT = new DataMapperImpl(new SchemaCacheImpl(new ConcurrentHashMap<>()));
private final @Nonnull SchemaCache schemaCache;
DataMapperImpl(@Nonnull SchemaCache schemaCache) {
this.schemaCache = schemaCache;
}
@Override
public void copyProperties(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Object dst,
@Nonnull Type dstType,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) throws ObjectConvertException {
try {
DataMapper.PropertyMapper propertyMapper = Option.findValue(ConvertOption.PROPERTY_MAPPER, options);
DataMapper.ExceptionHandler exceptionHandler = Option.findValue(ConvertOption.EXCEPTION_HANDLER, options);
if (src instanceof Map) {
MapSchemaParser mapSchemaParser = Fs.nonnull(
Option.findValue(ConvertOption.MAP_SCHEMA_PARSER),
MapSchemaParser.defaultParser()
);
MapSchema srcSchema = schemaCache.get(srcType, mapSchemaParser::parse).asMapSchema();
if (dst instanceof Map) {
MapSchema dstSchema = schemaCache.get(dstType, mapSchemaParser::parse).asMapSchema();
mapToMap(
Fs.as(src), srcSchema, Fs.as(dst), dstSchema, converter, propertyMapper, exceptionHandler, options
);
} else {
ObjectSchemaParser objectSchemaParser = Fs.nonnull(
Option.findValue(ConvertOption.OBJECT_SCHEMA_PARSER),
ObjectSchemaParser.defaultParser()
);
ObjectSchema dstSchema = schemaCache.get(dstType, objectSchemaParser::parse).asObjectSchema();
mapToObject(
Fs.as(src), srcSchema, dst, dstSchema, converter, propertyMapper, exceptionHandler, options
);
}
} else {
ObjectSchemaParser objectSchemaParser = Fs.nonnull(
Option.findValue(ConvertOption.OBJECT_SCHEMA_PARSER),
ObjectSchemaParser.defaultParser()
);
ObjectSchema srcSchema = schemaCache.get(srcType, objectSchemaParser::parse).asObjectSchema();
if (dst instanceof Map) {
MapSchemaParser mapSchemaParser = Fs.nonnull(
Option.findValue(ConvertOption.MAP_SCHEMA_PARSER),
MapSchemaParser.defaultParser()
);
MapSchema dstSchema = schemaCache.get(dstType, mapSchemaParser::parse).asMapSchema();
objectToMap(
src, srcSchema, Fs.as(dst), dstSchema, converter, propertyMapper, exceptionHandler, options
);
} else {
ObjectSchema dstSchema = schemaCache.get(dstType, objectSchemaParser::parse).asObjectSchema();
objectToObject(
src, srcSchema, dst, dstSchema, converter, propertyMapper, exceptionHandler, options
);
}
}
} catch (Exception e) {
throw new ObjectConvertException(e);
}
}
void mapToMap(
@Nonnull Map<Object, Object> src,
@Nonnull MapSchema srcSchema,
@Nonnull Map<Object, Object> dst,
@Nonnull MapSchema dstSchema,
@Nonnull ObjectConverter converter,
@Nullable DataMapper.PropertyMapper propertyMapper,
@Nullable DataMapper.ExceptionHandler exceptionHandler,
@Nonnull Option<?, ?> @Nonnull ... options
) {
src.forEach((srcKey, srcValue) -> {
try {
if (ignored(srcKey, options)) {
return;
}
Object dstPropertyName;
Object dstPropertyValue;
if (propertyMapper != null) {
Map.Entry<Object, Object> entry = propertyMapper.map(
srcKey, src, srcSchema, dst, dstSchema, converter, options
);
if (entry == null) {
return;
}
dstPropertyName = entry.getKey();
dstPropertyValue = entry.getValue();
} else {
if (srcValue == null && ignoredNull(options)) {
return;
}
dstPropertyName = converter.convert(
srcKey, srcSchema.keyType(), dstSchema.keyType(), options);
dstPropertyValue = converter.convert(
srcValue, srcSchema.valueType(), dstSchema.valueType(), options);
}
dst.put(dstPropertyName, dstPropertyValue);
} catch (Exception e) {
if (exceptionHandler != null) {
try {
exceptionHandler.handle(e, srcKey, src, srcSchema, dst, dstSchema, converter, options);
} catch (Exception ex) {
throw new ObjectConvertException(ex);
}
} else {
throw e;
}
}
});
}
void mapToObject(
@Nonnull Map<Object, Object> src,
@Nonnull MapSchema srcSchema,
@Nonnull Object dst,
@Nonnull ObjectSchema dstSchema,
@Nonnull ObjectConverter converter,
@Nullable DataMapper.PropertyMapper propertyMapper,
@Nullable DataMapper.ExceptionHandler exceptionHandler,
@Nonnull Option<?, ?> @Nonnull ... options
) {
src.forEach((srcKey, srcValue) -> {
try {
if (ignored(srcKey, options)) {
return;
}
Object dstPropertyName;
Object dstPropertyValue;
ObjectProperty dstProperty;
if (propertyMapper != null) {
Map.Entry<Object, Object> entry = propertyMapper.map(
srcKey, src, srcSchema, dst, dstSchema, converter, options
);
if (entry == null) {
return;
}
dstPropertyName = entry.getKey();
dstPropertyValue = entry.getValue();
dstProperty = dstSchema.getProperty((String) dstPropertyName);
if (dstProperty == null || !dstProperty.isWritable()) {
return;
}
} else {
if (srcValue == null && ignoredNull(options)) {
return;
}
dstPropertyName = converter.convert(srcKey, srcSchema.keyType(), String.class, options);
dstProperty = dstSchema.getProperty((String) dstPropertyName);
if (dstProperty == null || !dstProperty.isWritable()) {
return;
}
dstPropertyValue = converter.convert(srcValue, srcSchema.valueType(), dstProperty.type(), options);
}
dstProperty.setValue(dst, dstPropertyValue);
} catch (Exception e) {
if (exceptionHandler != null) {
try {
exceptionHandler.handle(e, srcKey, src, srcSchema, dst, dstSchema, converter, options);
} catch (Exception ex) {
throw new ObjectConvertException(ex);
}
} else {
throw e;
}
}
});
}
void objectToMap(
@Nonnull Object src,
@Nonnull ObjectSchema srcSchema,
@Nonnull Map<Object, Object> dst,
@Nonnull MapSchema dstSchema,
@Nonnull ObjectConverter converter,
@Nullable DataMapper.PropertyMapper propertyMapper,
@Nullable DataMapper.ExceptionHandler exceptionHandler,
@Nonnull Option<?, ?> @Nonnull ... options
) {
srcSchema.properties().forEach((srcPropertyName, srcProperty) -> {
try {
if (ignored(srcPropertyName, options)) {
return;
}
if (!srcProperty.isReadable()) {
return;
}
// do not map "class"
if ("class".equals(srcPropertyName)) {
return;
}
Object dstPropertyName;
Object dstPropertyValue;
if (propertyMapper != null) {
Map.Entry<Object, Object> entry = propertyMapper.map(
srcPropertyName, src, srcSchema, dst, dstSchema, converter, options
);
if (entry == null) {
return;
}
dstPropertyName = entry.getKey();
dstPropertyValue = entry.getValue();
} else {
Object srcPropertyValue = srcProperty.getValue(src);
if (srcPropertyValue == null && ignoredNull(options)) {
return;
}
dstPropertyName = converter.convert(srcPropertyName, String.class, dstSchema.keyType(), options);
dstPropertyValue = converter.convert(
srcPropertyValue, srcProperty.type(), dstSchema.valueType(), options);
}
dst.put(dstPropertyName, dstPropertyValue);
} catch (Exception e) {
if (exceptionHandler != null) {
try {
exceptionHandler.handle(e, srcPropertyName, src, srcSchema, dst, dstSchema, converter, options);
} catch (Exception ex) {
throw new ObjectConvertException(ex);
}
} else {
throw e;
}
}
});
}
void objectToObject(
@Nonnull Object src,
@Nonnull ObjectSchema srcSchema,
@Nonnull Object dst,
@Nonnull ObjectSchema dstSchema,
@Nonnull ObjectConverter converter,
@Nullable DataMapper.PropertyMapper propertyMapper,
@Nullable DataMapper.ExceptionHandler exceptionHandler,
@Nonnull Option<?, ?> @Nonnull ... options
) {
srcSchema.properties().forEach((srcPropertyName, srcProperty) -> {
try {
if (ignored(srcPropertyName, options)) {
return;
}
if (!srcProperty.isReadable()) {
return;
}
Object dstPropertyName;
Object dstPropertyValue;
ObjectProperty dstProperty;
if (propertyMapper != null) {
Map.Entry<Object, Object> entry = propertyMapper.map(
srcPropertyName, src, srcSchema, dst, dstSchema, converter, options
);
if (entry == null) {
return;
}
dstPropertyName = entry.getKey();
dstPropertyValue = entry.getValue();
dstProperty = dstSchema.getProperty((String) dstPropertyName);
if (dstProperty == null || !dstProperty.isWritable()) {
return;
}
} else {
Object srcPropertyValue = srcProperty.getValue(src);
if (srcPropertyValue == null && ignoredNull(options)) {
return;
}
dstPropertyName = srcPropertyName;
dstProperty = dstSchema.getProperty((String) dstPropertyName);
if (dstProperty == null || !dstProperty.isWritable()) {
return;
}
dstPropertyValue = converter.convert(
srcPropertyValue, srcProperty.type(), dstProperty.type(), options);
}
dstProperty.setValue(dst, dstPropertyValue);
} catch (Exception e) {
if (exceptionHandler != null) {
try {
exceptionHandler.handle(e, srcPropertyName, src, srcSchema, dst, dstSchema, converter, options);
} catch (Exception ex) {
throw new ObjectConvertException(ex);
}
} else {
throw e;
}
}
});
}
private boolean ignored(@Nonnull Object propertyName, @Nonnull Option<?, ?> @Nonnull ... options) {
Object[] ignoredProperties = Option.findValue(ConvertOption.IGNORE_PROPERTIES, options);
if (ignoredProperties == null) {
return false;
}
return ArrayKit.indexOf(ignoredProperties, propertyName) >= 0;
}
private boolean ignoredNull(@Nonnull Option<?, ?> @Nonnull ... options) {
return Option.containsKey(ConvertOption.IGNORE_NULL, options);
}
static final class SchemaCacheImpl implements SchemaCache {
private final @Nonnull Map<@Nonnull Type, @Nonnull DataSchema> map;
SchemaCacheImpl(@Nonnull Map<@Nonnull Type, @Nonnull DataSchema> map) {
this.map = map;
}
@Override
public @Nonnull DataSchema get(
@Nonnull Type type,
@Nonnull Function<? super @Nonnull Type, ? extends @Nonnull DataSchema> loader
) throws ObjectConvertException {
return map.computeIfAbsent(type, loader);
}
}
}