RangeMapDeserializer.java
package tools.jackson.datatype.guava.deser;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import com.google.common.collect.*;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.*;
import tools.jackson.databind.deser.NullValueProvider;
import tools.jackson.databind.deser.impl.NullsConstantProvider;
import tools.jackson.databind.deser.std.StdDeserializer;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.type.LogicalType;
import tools.jackson.databind.type.MapLikeType;
import tools.jackson.databind.util.ClassUtil;
/**
* Jackson deserializer for a Guava {@link RangeMap}.
* <p>
* Only string serializable ranges are supported at this time.
*
* @author mcvayc
*/
public class RangeMapDeserializer<T extends RangeMap<Comparable<?>, Object>>
extends StdDeserializer<T>
{
private static final List<String> METHOD_NAMES = ImmutableList.of("copyOf", "create");
private final MapLikeType type;
private final KeyDeserializer keyDeserializer;
private final TypeDeserializer elementTypeDeserializer;
private final ValueDeserializer<?> elementDeserializer;
private final NullValueProvider nullProvider;
private final boolean isImmutable;
private final boolean skipNullValues;
/**
* Since we have to use a method to transform from a known range-map type into actual one, we'll
* resolve method just once, use it. Note that if this is set to null, we can just construct a
* {@link TreeRangeMap} instance and be done with it.
*/
private final Method creatorMethod;
public RangeMapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, ValueDeserializer<?> elementDeserializer,
boolean isImmutable
) {
this(type, keyDeserializer, elementTypeDeserializer, elementDeserializer,
findTransformer(type.getRawClass()), null, isImmutable);
}
public RangeMapDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, ValueDeserializer<?> elementDeserializer,
Method creatorMethod, NullValueProvider nvp, boolean isImmutable) {
super(type);
this.type = type;
this.keyDeserializer = keyDeserializer;
this.elementTypeDeserializer = elementTypeDeserializer;
this.elementDeserializer = elementDeserializer;
this.creatorMethod = creatorMethod;
this.nullProvider = nvp;
this.isImmutable = isImmutable;
skipNullValues = (nvp == null) ? false : NullsConstantProvider.isSkipper(nvp);
}
private static Method findTransformer(Class<?> rawType) {
if (rawType == TreeRangeMap.class) {
return null;
}
// First, check type itself for matching methods
for (String methodName : METHOD_NAMES) {
try {
Method m = rawType.getDeclaredMethod(methodName, RangeMap.class);
if (m != null) {
return m;
}
} catch (NoSuchMethodException e) {
}
}
// If not working, possibly super types too (should we?)
for (String methodName : METHOD_NAMES) {
try {
Method m = rawType.getMethod(methodName, RangeMap.class);
if (m != null) {
return m;
}
} catch (NoSuchMethodException e) {
}
}
return null;
}
@Override
public LogicalType logicalType() {
return LogicalType.Map;
}
/**
* We need to use this method to properly handle possible contextual variants of key and value
* deserializers, as well as type deserializers.
*/
@Override
public ValueDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property)
throws JacksonException
{
KeyDeserializer kd = keyDeserializer;
if (kd == null) {
kd = ctxt.findKeyDeserializer(type.getKeyType(), property);
}
ValueDeserializer<?> valueDeser = elementDeserializer;
final JavaType vt = type.getContentType();
if (valueDeser == null) {
valueDeser = ctxt.findContextualValueDeserializer(vt, property);
} else { // if directly assigned, probably not yet contextual, so:
valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt);
}
// Type deserializer is slightly different; must be passed, but needs to become contextual:
TypeDeserializer vtd = elementTypeDeserializer;
if (vtd != null) {
vtd = vtd.forProperty(property);
}
return new RangeMapDeserializer<>(type, kd, vtd, valueDeser, creatorMethod,
findContentNullProvider(ctxt, property, valueDeser), isImmutable);
}
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt)
throws JacksonException
{
RangeMap rangeMap = TreeRangeMap.create();
JsonToken currToken = p.currentToken();
if (currToken != JsonToken.PROPERTY_NAME) {
if (currToken != JsonToken.END_OBJECT) {
expect(ctxt, JsonToken.START_OBJECT, currToken);
currToken = p.nextToken();
}
}
for (; currToken == JsonToken.PROPERTY_NAME; currToken = p.nextToken()) {
final Range<Comparable<?>> key = (Range<Comparable<?>>) keyDeserializer.deserializeKey(p.currentName(), ctxt);
p.nextToken();
final Object value;
if (p.currentToken() == JsonToken.VALUE_NULL) {
if (skipNullValues) {
continue;
}
value = nullProvider.getNullValue(ctxt);
} else if (elementTypeDeserializer != null) {
value = elementDeserializer.deserializeWithType(p, ctxt, elementTypeDeserializer);
} else {
value = elementDeserializer.deserialize(p, ctxt);
}
rangeMap.put(key, value);
}
if (creatorMethod == null) {
return (T) rangeMap;
}
try {
T map = (T) creatorMethod.invoke(null, rangeMap);
return map;
} catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException e) {
T result = (T) ctxt.handleInstantiationProblem(handledType(), rangeMap, e);
return result;
}
}
private void expect(DeserializationContext context,
JsonToken expected, JsonToken actual)
{
if (actual != expected) {
context.reportInputMismatch(this, String.format("Problem deserializing %s: expecting %s, found %s",
ClassUtil.getTypeDescription(getValueType()), expected, actual));
}
}
}