CommonConvertHandler.java
package space.sunqian.fs.object.convert.handlers;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.fs.Fs;
import space.sunqian.fs.base.date.DateFormatter;
import space.sunqian.fs.base.lang.EnumKit;
import space.sunqian.fs.base.number.NumFormatter;
import space.sunqian.fs.base.number.NumKit;
import space.sunqian.fs.base.option.Option;
import space.sunqian.fs.collect.ArrayKit;
import space.sunqian.fs.collect.ArrayOperator;
import space.sunqian.fs.collect.CollectKit;
import space.sunqian.fs.collect.ListKit;
import space.sunqian.fs.collect.MapKit;
import space.sunqian.fs.collect.SetKit;
import space.sunqian.fs.io.BufferKit;
import space.sunqian.fs.io.IOOperator;
import space.sunqian.fs.object.builder.BuilderOperator;
import space.sunqian.fs.object.builder.BuilderOperatorProvider;
import space.sunqian.fs.object.convert.ConvertOption;
import space.sunqian.fs.object.convert.ObjectConverter;
import space.sunqian.fs.object.convert.ObjectCopier;
import space.sunqian.fs.reflect.ReflectionException;
import space.sunqian.fs.reflect.TypeKit;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.IntFunction;
/**
* The common implementation of {@link ObjectConverter.Handler}, also be the default last handler of
* {@link ObjectConverter#defaultConverter()}. Using {@link #getInstance()} can get a same one instance of this
* handler.
* <p>
* This handler providers the common conversion logic for all types. This is a table showing the conversion logic of
* this handler for different target types:
* <table summary="Conversion Logic">
* <tr>
* <th>Target</th>
* <th>Source</th>
* <th>Conversion Logic</th>
* </tr>
* <tr>
* <td rowspan="6">{@link String}, {@link CharSequence}</td>
* <td>{@link InputStream}, {@link ReadableByteChannel}, {@link Reader}, {@link ByteBuffer}, {@code byte[]}</td>
* <td>Using {@link ConvertOption#ioOperator(IOOperator)} and {@link ConvertOption#charset(Charset)}
* to decode to string.</td>
* </tr>
* <tr>
* <td>{@code char[]}</td>
* <td>Using {@link String#String(char[])} to construct to string.</td>
* </tr>
* <tr>
* <td>{@link BigDecimal}</td>
* <td>Using {@link BigDecimal#toPlainString()}.</td>
* </tr>
* <tr>
* <td>Date and Time Objects</td>
* <td>Using {@link ConvertOption#dateFormatter(DateFormatter)} to handle.</td>
* </tr>
* <tr>
* <td>Numbers</td>
* <td>Using {@link NumFormatter#format(Number)}.</td>
* </tr>
* <tr>
* <td>Others</td>
* <td>Using {@link Object#toString()}.</td>
* </tr>
* <tr>
* <td>{@code byte[]}, {@code char[]}, {@link ByteBuffer}, {@link CharBuffer}</td>
* <td>{@link String}</td>
* <td>Using {@link String#getBytes(Charset)} or {@link String#toCharArray()}.</td>
* </tr>
* <tr>
* <td rowspan="2">Numbers</td>
* <td>{@link String}</td>
* <td>Using {@link NumKit#toNumber(CharSequence, Class)}.</td>
* </tr>
* <tr>
* <td>Other Numbers</td>
* <td>Using {@link NumKit#toNumber(Number, Class)}.</td>
* </tr>
* <tr>
* <td>{@code long} and {@link Long}</td>
* <td>Date and Time</td>
* <td>Returns epoch milliseconds of the date or time object.</td>
* </tr>
* <tr>
* <td rowspan="2">{@code boolean} and {@link Boolean}</td>
* <td>Numbers</td>
* <td>{@code false} for {@code 0}, otherwise {@code true}.</td>
* </tr>
* <tr>
* <td>{@link String}</td>
* <td>{@code true} for {@code equalsIgnoreCase("true")}, otherwise {@code false}.</td>
* </tr>
* <tr>
* <td rowspan="2">Date and Time</td>
* <td>{@link String} and Other Date Time Objects</td>
* <td>Using {@link ConvertOption#dateFormatter(DateFormatter)} to handle.</td>
* </tr>
* <tr>
* <td>{@code long} and {@link Long}</td>
* <td>Treated as an epoch milliseconds, then using {@link ConvertOption#dateFormatter(DateFormatter)} to
* handle.</td>
* </tr>
* <tr>
* <td>Enums</td>
* <td>Any Objects</td>
* <td>Using {@link EnumKit#findEnum(Class, String)} with the name from {@link Object#toString()}.</td>
* </tr>
* <tr>
* <td>Array and Collection Objects</td>
* <td>Array or Iterable Objects</td>
* <td>Array created using reflection. Collection created using its constructor, the supported collection types
* follow the {@link SetKit#newFunction(Type)} and {@link ListKit#newFunction(Type)}. After creating the container,
* uses the {@code converter} parameter to handle component types.
* </td>
* </tr>
* <tr>
* <td>Map and Data Objects</td>
* <td>Any Objects</td>
* <td>Generating data object is based on {@link ConvertOption#builderOperatorProvider(BuilderOperatorProvider)} and
* {@link ConvertOption#objectCopier(ObjectCopier)}. Generating map using its constructor, and copying properties
* also using {@link ConvertOption#objectCopier(ObjectCopier)}. The supported map types follow the
* {@link MapKit#newFunction(Type)}.
* </td>
* </tr>
* </table>
* <p>
* Note:
* <ul>
* <li>
* This handler typically creates new objects for each conversion, and does not perform the same handing as
* {@link AssignableConvertHandler};
* </li>
* <li>
* This handler directly returns {@code null} when the source is {@code null};
* </li>
* </ul>
*
* @author SunQian
*/
public class CommonConvertHandler implements ObjectConverter.Handler {
private static final @Nonnull CommonConvertHandler INST = new CommonConvertHandler();
/**
* Returns a same one instance of this handler.
*/
public static @Nonnull CommonConvertHandler getInstance() {
return INST;
}
@Override
public Object convert(
@Nullable Object src,
@Nonnull Type srcType,
@Nonnull Type targetType,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) throws Exception {
if (src == null) {
return null;
}
if (targetType instanceof Class<?>) {
@SuppressWarnings("PatternVariableCanBeUsed")
Class<?> targetClass = (Class<?>) targetType;
// to enum:
if (targetClass.isEnum()) {
String name = src.toString();
return EnumKit.findEnum(Fs.as(targetClass), name);
}
// string to byte[], char[], ByteBuffer, CharBuffer:
if (srcType.equals(String.class)) {
if (targetType.equals(byte[].class) || targetType.equals(ByteBuffer.class)) {
Charset charset = ConvertOption.getCharset(options);
byte[] bytes = ((String) src).getBytes(charset);
return targetType.equals(ByteBuffer.class) ? ByteBuffer.wrap(bytes) : bytes;
}
if (targetType.equals(char[].class)) {
return ((String) src).toCharArray();
}
if (targetType.equals(CharBuffer.class)) {
return CharBuffer.wrap((String) src);
}
}
// to array:
if (targetClass.isArray()) {
return toArray(src, srcType, targetClass, converter, options);
}
ClassHandler classHandler = TargetClasses.get(targetClass);
// to target class:
if (classHandler != null) {
return classHandler.convert(src, srcType, targetClass, converter, options);
}
IntFunction<Collection<Object>> collectionFunc = collectionFunction(targetClass);
// to collection:
if (collectionFunc != null) {
return toCollection(
src, srcType, collectionFunc, targetClass.getTypeParameters()[0], converter, options
);
}
// to map or data object:
return toDataObject(src, srcType, targetClass, targetType, converter, options);
} else if (targetType instanceof GenericArrayType) {
// to generic array:
return toArray(src, srcType, (GenericArrayType) targetType, converter, options);
} else if (targetType instanceof ParameterizedType) {
@SuppressWarnings("PatternVariableCanBeUsed")
ParameterizedType paramType = (ParameterizedType) targetType;
Class<?> rawTarget = (Class<?>) paramType.getRawType();
IntFunction<Collection<Object>> collectionFunc = collectionFunction(rawTarget);
// to collection:
if (collectionFunc != null) {
return toCollection(
src, srcType, collectionFunc, paramType.getActualTypeArguments()[0], converter, options
);
}
// to map or data object:
return toDataObject(src, srcType, rawTarget, targetType, converter, options);
}
return ObjectConverter.Status.HANDLER_CONTINUE;
}
private @Nullable IntFunction<Collection<Object>> collectionFunction(@Nonnull Class<?> collectionType) {
if (Set.class.isAssignableFrom(collectionType)) {
return Fs.as(SetKit.newFunction(collectionType));
}
return Fs.as(ListKit.newFunction(collectionType));
}
private Object toArray(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
return toArray(
src,
srcType,
target,
target.getComponentType(),
converter,
options
);
}
private Object toArray(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull GenericArrayType target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
Class<?> targetClass = Fs.asNonnull(TypeKit.toRuntimeClass(target));
return toArray(
src,
srcType,
targetClass,
target.getGenericComponentType(),
converter,
options
);
}
private Object toArray(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull Type targetComponentType,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
if (srcType instanceof Class<?>) {
@SuppressWarnings("PatternVariableCanBeUsed")
Class<?> srcClass = (Class<?>) srcType;
if (srcClass.isArray()) {
ArrayOperator srcOperator = ArrayOperator.of(srcClass);
int size = srcOperator.size(src);
Object newArray = ArrayKit.newArray(target.getComponentType(), size);
Class<?> srcComponentType = srcClass.getComponentType();
ArrayOperator targetOperator = ArrayOperator.of(target);
for (int i = 0; i < size; i++) {
Object srcElement = srcOperator.get(src, i);
Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
targetOperator.set(newArray, i, targetElement);
}
return newArray;
}
}
Type srcComponentType = resolveComponentType(srcType);
if (srcComponentType == null) {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
Collection<?> srcCollection;
if (src instanceof Collection<?>) {
srcCollection = (Collection<?>) src;
} else if (src instanceof Iterable<?>) {
@SuppressWarnings("PatternVariableCanBeUsed")
Iterable<?> iter = (Iterable<?>) src;
srcCollection = CollectKit.addAll(new ArrayList<>(), iter);
} else {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
int size = srcCollection.size();
Object newArray = ArrayKit.newArray(target.getComponentType(), size);
ArrayOperator targetOperator = ArrayOperator.of(target);
int i = 0;
for (Object srcElement : srcCollection) {
Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
targetOperator.set(newArray, i++, targetElement);
}
return newArray;
}
private Object toCollection(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull IntFunction<Collection<Object>> collectionFunc,
@Nonnull Type targetComponentType,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
if (srcType instanceof Class<?>) {
@SuppressWarnings("PatternVariableCanBeUsed")
Class<?> srcClass = (Class<?>) srcType;
if (srcClass.isArray()) {
ArrayOperator srcOperator = ArrayOperator.of(srcClass);
int size = srcOperator.size(src);
Collection<Object> newCollection = collectionFunc.apply(size);
Class<?> srcComponentType = srcClass.getComponentType();
for (int i = 0; i < size; i++) {
Object srcElement = srcOperator.get(src, i);
Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
newCollection.add(targetElement);
}
return newCollection;
}
}
Type srcComponentType = resolveComponentType(srcType);
if (srcComponentType == null) {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
Collection<?> srcCollection;
if (src instanceof Collection<?>) {
srcCollection = (Collection<?>) src;
} else if (src instanceof Iterable<?>) {
@SuppressWarnings("PatternVariableCanBeUsed")
Iterable<?> iter = (Iterable<?>) src;
srcCollection = CollectKit.addAll(new ArrayList<>(), iter);
} else {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
int size = srcCollection.size();
Collection<Object> newCollection = collectionFunc.apply(size);
int i = 0;
for (Object srcElement : srcCollection) {
Object targetElement = converter.convert(srcElement, srcComponentType, targetComponentType, options);
newCollection.add(targetElement);
}
return newCollection;
}
private @Nullable Type resolveComponentType(@Nonnull Type type) {
try {
return TypeKit.resolveActualTypeArguments(type, Iterable.class).get(0);
} catch (ReflectionException e) {
return null;
}
}
private Object toDataObject(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> rawTarget,
@Nonnull Type target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) throws Exception {
IntFunction<Map<Object, Object>> mapFunc = MapKit.newFunction(rawTarget);
ObjectCopier objectCopier = ConvertOption.getObjectCopier(options);
if (mapFunc != null) {
Object targetObject = mapFunc.apply(0);
objectCopier.copyProperties(src, srcType, targetObject, target, converter, options);
return targetObject;
} else {
BuilderOperatorProvider builderProvider = ConvertOption.getBuilderOperatorProvider(options);
BuilderOperator operator = builderProvider.forType(target);
if (operator == null) {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
Object targetBuilder = operator.createBuilder();
objectCopier.copyProperties(src, srcType, targetBuilder, operator.builderType(), converter, options);
return operator.buildTarget(targetBuilder);
}
}
private interface ClassHandler {
Object convert(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) throws Exception;
}
private static final class TargetClasses {
private static final @Nonnull Map<@Nonnull Type, @Nonnull ClassHandler> HANDLER_MAP;
static {
HANDLER_MAP = new HashMap<>();
HANDLER_MAP.put(String.class, StringClassHandler.INST);
HANDLER_MAP.put(CharSequence.class, StringClassHandler.INST);
HANDLER_MAP.put(boolean.class, BooleanClassHandler.INST);
HANDLER_MAP.put(byte.class, NumberClassHandler.INST);
HANDLER_MAP.put(short.class, NumberClassHandler.INST);
HANDLER_MAP.put(char.class, NumberClassHandler.INST);
HANDLER_MAP.put(int.class, NumberClassHandler.INST);
HANDLER_MAP.put(long.class, NumberClassHandler.INST);
HANDLER_MAP.put(float.class, NumberClassHandler.INST);
HANDLER_MAP.put(double.class, NumberClassHandler.INST);
HANDLER_MAP.put(Boolean.class, BooleanClassHandler.INST);
HANDLER_MAP.put(Byte.class, NumberClassHandler.INST);
HANDLER_MAP.put(Short.class, NumberClassHandler.INST);
HANDLER_MAP.put(Character.class, NumberClassHandler.INST);
HANDLER_MAP.put(Integer.class, NumberClassHandler.INST);
HANDLER_MAP.put(Long.class, NumberClassHandler.INST);
HANDLER_MAP.put(Float.class, NumberClassHandler.INST);
HANDLER_MAP.put(Double.class, NumberClassHandler.INST);
HANDLER_MAP.put(BigInteger.class, NumberClassHandler.INST);
HANDLER_MAP.put(BigDecimal.class, NumberClassHandler.INST);
HANDLER_MAP.put(Number.class, NumberClassHandler.INST);
HANDLER_MAP.put(Date.class, DateClassHandler.INST);
HANDLER_MAP.put(Instant.class, DateClassHandler.INST);
HANDLER_MAP.put(LocalDateTime.class, DateClassHandler.INST);
HANDLER_MAP.put(ZonedDateTime.class, DateClassHandler.INST);
HANDLER_MAP.put(OffsetDateTime.class, DateClassHandler.INST);
HANDLER_MAP.put(LocalDate.class, DateClassHandler.INST);
HANDLER_MAP.put(LocalTime.class, DateClassHandler.INST);
}
public static @Nullable CommonConvertHandler.ClassHandler get(@Nonnull Class<?> target) {
return HANDLER_MAP.get(target);
}
}
private enum StringClassHandler implements ClassHandler {
INST;
@Override
public Object convert(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) throws Exception {
if (src instanceof char[]) {
return new String((char[]) src);
}
if (src instanceof Number) {
NumFormatter numFormatter = ConvertOption.getNumFormatter(options);
return numFormatter.format((Number) src);
}
if (src instanceof Date) {
DateFormatter dateFormatter = ConvertOption.getDateFormatter(options);
return dateFormatter.format((Date) src);
}
if (src instanceof TemporalAccessor) {
DateFormatter dateFormatter = ConvertOption.getDateFormatter(options);
return dateFormatter.format((TemporalAccessor) src);
}
Charset charset = ConvertOption.getCharset(options);
if (src instanceof byte[]) {
return new String((byte[]) src, charset);
}
if (src instanceof ByteBuffer) {
return BufferKit.string((ByteBuffer) src, charset);
}
IOOperator ioOperator = ConvertOption.getIOOperator(options);
if (src instanceof Reader) {
return ioOperator.string((Reader) src);
}
if (src instanceof InputStream) {
return ioOperator.string((InputStream) src, charset);
}
if (src instanceof ReadableByteChannel) {
return ioOperator.string((ReadableByteChannel) src, charset);
}
return src.toString();
}
}
private enum NumberClassHandler implements ClassHandler {
INST;
@Override
public Object convert(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
if (src instanceof String) {
return NumKit.toNumber((String) src, target);
}
// date to long
if (target.equals(Long.class) || target.equals(long.class)) {
if (src instanceof Date) {
return ((Date) src).getTime();
}
if (src instanceof TemporalAccessor) {
@SuppressWarnings("PatternVariableCanBeUsed")
TemporalAccessor ta = (TemporalAccessor) src;
DateFormatter dateFormatter = ConvertOption.getDateFormatter(options);
Date date = dateFormatter.convert(ta, Date.class);
return date.getTime();
}
}
if (src instanceof Number) {
@SuppressWarnings("PatternVariableCanBeUsed")
Number srcNum = (Number) src;
return NumKit.toNumber(srcNum, target);
}
return ObjectConverter.Status.HANDLER_CONTINUE;
}
}
private enum BooleanClassHandler implements ClassHandler {
INST;
@Override
public Object convert(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
if (src instanceof Boolean) {
return src;
}
if (src instanceof Number) {
return ((Number) src).intValue() != 0;
}
if (src instanceof String) {
return "true".equalsIgnoreCase((String) src);
}
return ObjectConverter.Status.HANDLER_CONTINUE;
}
}
private enum DateClassHandler implements ClassHandler {
INST;
@Override
public Object convert(
@Nonnull Object src,
@Nonnull Type srcType,
@Nonnull Class<?> target,
@Nonnull ObjectConverter converter,
@Nonnull Option<?, ?> @Nonnull ... options
) {
DateFormatter dateFormatter = ConvertOption.getDateFormatter(options);
if (src instanceof String) {
return dateFormatter.parse((String) src, target);
}
if (src instanceof Date) {
return dateFormatter.convert((Date) src, target);
}
if (src instanceof Long) {
Date date = new Date((Long) src);
return dateFormatter.convert(date, target);
}
if (src instanceof TemporalAccessor) {
return dateFormatter.convert((TemporalAccessor) src, target);
}
return ObjectConverter.Status.HANDLER_CONTINUE;
}
}
}