AssignableConvertHandler.java
package space.sunqian.fs.object.convert.handlers;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.fs.base.option.Option;
import space.sunqian.fs.object.convert.ConvertOption;
import space.sunqian.fs.object.convert.ObjectConverter;
import space.sunqian.fs.reflect.TypeKit;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Objects;
/**
* The default first {@link ObjectConverter.Handler} of {@link ObjectConverter#defaultConverter()}, mainly used to
* determine whether it is possible to return the source object directly without creating a new object. Using
* {@link #getInstance()} can get a same one instance of this handler.
* <p>
* Its conversion logic is:
* <ol>
* <li>
* If the specified source type is a {@link WildcardType} or {@link TypeVariable}, and it represents {@code ?}
* or {@code ? extends Object} or raw {@code T} or {@code T extends Object}, this handler will use
* {@link Object#getClass()} as the actual source type (or {@code Object.class} if the source object is
* {@code null}) to convert, the codes are simplified as:
* {@code return converter.convert(src, src == null ? Object.class : src.getClass(), targetType, options)};
* </li>
* <li>
* If the conversion enables {@link ConvertOption#NEW_INSTANCE_MODE}, returns
* {@link ObjectConverter.Status#HANDLER_CONTINUE} for any source type;
* </li>
* <li>
* If the specified source type equals to the target type, returns the source object itself;
* </li>
* <li>
* If the conversion enables {@link ConvertOption#STRICT_TARGET_TYPE_MODE}, returns
* {@link ObjectConverter.Status#HANDLER_CONTINUE} for target type of {@link WildcardType};
* Otherwise, recursively convert their lower/upper bounds type via the {@code converter} parameter;
* </li>
* <li>
* Using {@link TypeKit#isCompatible(Type, Type)} to check if the target type is compatible with the specified
* source type, and returns the source object itself if it is compatible;
* </li>
* <li>
* Otherwise, returns {@link ObjectConverter.Status#HANDLER_CONTINUE}.
* </li>
* </ol>
*
* @author sunqian
*/
public class AssignableConvertHandler implements ObjectConverter.Handler {
private static final @Nonnull AssignableConvertHandler INST = new AssignableConvertHandler();
/**
* Returns a same one instance of this handler.
*/
public static @Nonnull AssignableConvertHandler 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 (srcType instanceof WildcardType) {
if (isUndefined((WildcardType) srcType)) {
return converter.convert(src, ensureType(src), targetType, options);
}
}
if (srcType instanceof TypeVariable) {
if (isUndefined((TypeVariable<?>) srcType)) {
return converter.convert(src, ensureType(src), targetType, options);
}
}
if (ConvertOption.isNewInstanceMode(options)) {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
if (Objects.equals(targetType, srcType)) {
return src;
}
if (ConvertOption.isStrictTargetTypeMode(options)) {
// strict mode, wildcard is unsupported
if (targetType instanceof WildcardType) {
return ObjectConverter.Status.HANDLER_CONTINUE;
}
} else {
// non-strict mode, wildcard and type variable will be considered as their bounds type
if (targetType instanceof WildcardType) {
@SuppressWarnings("PatternVariableCanBeUsed")
WildcardType wildcard = (WildcardType) targetType;
Type superType = TypeKit.getLowerBound(wildcard);
superType = superType != null ? superType : TypeKit.getUpperBound(wildcard);
return converter.convert(src, srcType, superType, options);
}
if (targetType instanceof TypeVariable<?>) {
return converter.convert(src, srcType, ((TypeVariable<?>) targetType).getBounds()[0], options);
}
}
if (TypeKit.isCompatible(targetType, srcType)) {
return src;
}
return ObjectConverter.Status.HANDLER_CONTINUE;
}
private boolean isUndefined(WildcardType type) {
Type lowerBound = TypeKit.getLowerBound(type);
if (lowerBound == null) {
Type upperBound = TypeKit.getUpperBound(type);
return Object.class.equals(upperBound);
}
return false;
}
private boolean isUndefined(TypeVariable<?> type) {
Type upperBound = TypeKit.getFirstBound(type);
return Object.class.equals(upperBound);
}
private @Nonnull Type ensureType(@Nullable Object src) {
return src == null ? Object.class : src.getClass();
}
}