package org.springframework.data.util;
import scala.Function0;
import scala.Option;
import scala.runtime.AbstractFunction0;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import com.google.common.base.Optional;
public abstract class NullableWrapperConverters {
private static final boolean GUAVA_PRESENT = ClassUtils.isPresent("com.google.common.base.Optional",
NullableWrapperConverters.class.getClassLoader());
private static final boolean SCALA_PRESENT = ClassUtils.isPresent("scala.Option",
NullableWrapperConverters.class.getClassLoader());
private static final boolean VAVR_PRESENT = ClassUtils.isPresent("io.vavr.control.Option",
NullableWrapperConverters.class.getClassLoader());
private static final Set<WrapperType> WRAPPER_TYPES = new HashSet<WrapperType>();
private static final Set<WrapperType> UNWRAPPER_TYPES = new HashSet<WrapperType>();
private static final Set<Converter<Object, Object>> UNWRAPPERS = new HashSet<Converter<Object, Object>>();
private static final Map<Class<?>, Boolean> supportsCache = new ConcurrentReferenceHashMap<>();
static {
WRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToJdk8OptionalConverter.getWrapperType());
UNWRAPPERS.add(Jdk8OptionalUnwrapper.INSTANCE);
if (GUAVA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToGuavaOptionalConverter.getWrapperType());
UNWRAPPERS.add(GuavaOptionalUnwrapper.INSTANCE);
}
if (SCALA_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
UNWRAPPER_TYPES.add(NullableWrapperToScalaOptionConverter.getWrapperType());
UNWRAPPERS.add(ScalOptionUnwrapper.INSTANCE);
}
if (VAVR_PRESENT) {
WRAPPER_TYPES.add(NullableWrapperToVavrOptionConverter.getWrapperType());
UNWRAPPERS.add(VavrOptionUnwrapper.INSTANCE);
}
}
private NullableWrapperConverters() {}
public static boolean supports(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
return supportsCache.computeIfAbsent(type, key -> {
for (WrapperType candidate : WRAPPER_TYPES) {
if (candidate.getType().isAssignableFrom(key)) {
return true;
}
}
return false;
});
}
public static boolean supportsUnwrapping(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
for (WrapperType candidate : UNWRAPPER_TYPES) {
if (candidate.getType().isAssignableFrom(type)) {
return true;
}
}
return false;
}
public static boolean isSingleValue(Class<?> type) {
for (WrapperType candidate : WRAPPER_TYPES) {
if (candidate.getType().isAssignableFrom(type)) {
return candidate.isSingleValue();
}
}
return false;
}
public static void registerConvertersIn(ConverterRegistry registry) {
Assert.notNull(registry, "ConversionService must not be null!");
registry.addConverter(NullableWrapperToJdk8OptionalConverter.INSTANCE);
if (GUAVA_PRESENT) {
registry.addConverter(NullableWrapperToGuavaOptionalConverter.INSTANCE);
}
if (SCALA_PRESENT) {
registry.addConverter(NullableWrapperToScalaOptionConverter.INSTANCE);
}
if (VAVR_PRESENT) {
registry.addConverter(NullableWrapperToVavrOptionConverter.INSTANCE);
}
}
@Nullable
public static Object unwrap(@Nullable Object source) {
if (source == null || !supports(source.getClass())) {
return source;
}
for (Converter<Object, Object> converter : UNWRAPPERS) {
Object result = converter.convert(source);
if (result != source) {
return result;
}
}
return source;
}
public static TypeInformation<?> unwrapActualType(TypeInformation<?> type) {
Assert.notNull(type, "type must not be null");
Class<?> rawType = type.getType();
boolean needToUnwrap = supports(rawType)
|| Stream.class.isAssignableFrom(rawType);
return needToUnwrap ? unwrapActualType(type.getRequiredComponentType()) : type;
}
private static abstract class AbstractWrapperTypeConverter implements GenericConverter {
private final Object nullValue;
private final Iterable<Class<?>> wrapperTypes;
protected AbstractWrapperTypeConverter(Object nullValue) {
Assert.notNull(nullValue, "Null value must not be null!");
this.nullValue = nullValue;
this.wrapperTypes = Collections.singleton(nullValue.getClass());
}
public AbstractWrapperTypeConverter(Object nullValue, Iterable<Class<?>> wrapperTypes) {
this.nullValue = nullValue;
this.wrapperTypes = wrapperTypes;
}
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Streamable.of(wrapperTypes)
.map(it -> new ConvertiblePair(NullableWrapper.class, it))
.stream().collect(StreamUtils.toUnmodifiableSet());
}
@Nullable
@Override
public final Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
NullableWrapper wrapper = (NullableWrapper) source;
Object value = wrapper.getValue();
return value == null ? nullValue : wrap(value);
}
protected abstract Object wrap(Object source);
}
private static class NullableWrapperToJdk8OptionalConverter extends AbstractWrapperTypeConverter {
public static final NullableWrapperToJdk8OptionalConverter INSTANCE = new NullableWrapperToJdk8OptionalConverter();
private NullableWrapperToJdk8OptionalConverter() {
super(java.util.Optional.empty());
}
@Override
protected Object wrap(Object source) {
return java.util.Optional.of(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(java.util.Optional.class);
}
}
private static class NullableWrapperToGuavaOptionalConverter extends AbstractWrapperTypeConverter {
public static final NullableWrapperToGuavaOptionalConverter INSTANCE = new NullableWrapperToGuavaOptionalConverter();
private NullableWrapperToGuavaOptionalConverter() {
super(Optional.absent(), Collections.singleton(Optional.class));
}
@Override
protected Object wrap(Object source) {
return Optional.of(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(Optional.class);
}
}
private static class NullableWrapperToScalaOptionConverter extends AbstractWrapperTypeConverter {
public static final NullableWrapperToScalaOptionConverter INSTANCE = new NullableWrapperToScalaOptionConverter();
private NullableWrapperToScalaOptionConverter() {
super(Option.empty(), Collections.singleton(Option.class));
}
@Override
protected Object wrap(Object source) {
return Option.apply(source);
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(Option.class);
}
}
private static class NullableWrapperToVavrOptionConverter extends AbstractWrapperTypeConverter {
public static final NullableWrapperToVavrOptionConverter INSTANCE = new NullableWrapperToVavrOptionConverter();
private NullableWrapperToVavrOptionConverter() {
super(io.vavr.control.Option.none(), Collections.singleton(io.vavr.control.Option.class));
}
public static WrapperType getWrapperType() {
return WrapperType.singleValue(io.vavr.control.Option.class);
}
@Override
protected Object wrap(Object source) {
return io.vavr.control.Option.of(source);
}
}
private enum GuavaOptionalUnwrapper implements Converter<Object, Object> {
INSTANCE;
@Nullable
@Override
public Object convert(Object source) {
return source instanceof Optional ? ((Optional<?>) source).orNull() : source;
}
}
private enum Jdk8OptionalUnwrapper implements Converter<Object, Object> {
INSTANCE;
@Nullable
@Override
public Object convert(Object source) {
return source instanceof java.util.Optional ? ((java.util.Optional<?>) source).orElse(null) : source;
}
}
private enum ScalOptionUnwrapper implements Converter<Object, Object> {
INSTANCE;
private final Function0<Object> alternative = new AbstractFunction0<Object>() {
@Nullable
@Override
public Option<Object> apply() {
return null;
}
};
@Nullable
@Override
public Object convert(Object source) {
return source instanceof Option ? ((Option<?>) source).getOrElse(alternative) : source;
}
}
private enum VavrOptionUnwrapper implements Converter<Object, Object> {
INSTANCE;
@Nullable
@Override
@SuppressWarnings("unchecked")
public Object convert(Object source) {
if (source instanceof io.vavr.control.Option) {
return ((io.vavr.control.Option<Object>) source).getOrElse(() -> null);
}
return source;
}
}
private static final class WrapperType {
private WrapperType(Class<?> type, Cardinality cardinality) {
this.type = type;
this.cardinality = cardinality;
}
Class<?> getType() {
return this.type;
}
Cardinality getCardinality() {
return cardinality;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrapperType)) {
return false;
}
WrapperType that = (WrapperType) o;
if (!ObjectUtils.nullSafeEquals(type, that.type)) {
return false;
}
return cardinality == that.cardinality;
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(type);
result = 31 * result + ObjectUtils.nullSafeHashCode(cardinality);
return result;
}
@Override
public String toString() {
return "WrapperType(type=" + this.getType() + ", cardinality=" + this.getCardinality() + ")";
}
enum Cardinality {
NONE, SINGLE, MULTI;
}
private final Class<?> type;
private final Cardinality cardinality;
static WrapperType singleValue(Class<?> type) {
return new WrapperType(type, Cardinality.SINGLE);
}
static WrapperType multiValue(Class<?> type) {
return new WrapperType(type, Cardinality.MULTI);
}
static WrapperType noValue(Class<?> type) {
return new WrapperType(type, Cardinality.NONE);
}
boolean isSingleValue() {
return cardinality.equals(Cardinality.SINGLE);
}
}
}