GuavaCacheDeserializer.java
package com.fasterxml.jackson.datatype.guava.deser.cache;
import java.io.IOException;
import com.google.common.cache.Cache;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.LogicalType;
import com.fasterxml.jackson.databind.type.MapLikeType;
import com.fasterxml.jackson.databind.util.ClassUtil;
public abstract class GuavaCacheDeserializer<T extends Cache<Object, Object>>
extends StdDeserializer<T> implements ContextualDeserializer
{
private static final long serialVersionUID = 1L;
private final MapLikeType type;
private final KeyDeserializer keyDeserializer;
private final TypeDeserializer elementTypeDeserializer;
private final JsonDeserializer<?> elementDeserializer;
/*
* @since 2.16 : in 3.x demote to `ContainerDeserializerBase`
*/
private final NullValueProvider nullProvider;
private final boolean skipNullValues;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public GuavaCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer) {
this(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, null);
}
public GuavaCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
NullValueProvider nvp)
{
super(type);
this.type = type;
this.keyDeserializer = keyDeserializer;
this.elementTypeDeserializer = elementTypeDeserializer;
this.elementDeserializer = elementDeserializer;
this.nullProvider = nvp;
skipNullValues = (nvp == null) ? false : NullsConstantProvider.isSkipper(nvp);
}
/*
/**********************************************************
/* Post-processing (contextualization)
/**********************************************************
*/
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException
{
KeyDeserializer kd = keyDeserializer;
if (kd == null) {
kd = ctxt.findKeyDeserializer(type.getKeyType(), property);
}
JsonDeserializer<?> 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 _createContextual(type, kd, vtd, valueDeser,
findContentNullProvider(ctxt, property, valueDeser));
}
/*
/**********************************************************************
/* Abstract methods for subclasses
/**********************************************************************
*/
protected abstract T createCache();
protected abstract JsonDeserializer<?> _createContextual(MapLikeType t, KeyDeserializer kd,
TypeDeserializer vtd, JsonDeserializer<?> vd, NullValueProvider np);
/*
/**********************************************************************
/* Implementations
/**********************************************************************
*/
@Override
public LogicalType logicalType() {
return LogicalType.Map;
}
@Override
public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return deserializeContents(p, ctxt);
}
private T deserializeContents(JsonParser p, DeserializationContext ctxt)
throws IOException
{
T cache = createCache();
JsonToken currToken = p.currentToken();
if (currToken != JsonToken.FIELD_NAME) {
// 01-Mar-2023, tatu: [datatypes-collections#104] Handle empty Maps too
if (currToken != JsonToken.END_OBJECT) {
expect(p, JsonToken.START_OBJECT);
currToken = p.nextToken();
}
}
for (; currToken == JsonToken.FIELD_NAME; currToken = p.nextToken()) {
final Object key;
if (keyDeserializer != null) {
key = keyDeserializer.deserializeKey(p.currentName(), ctxt);
} else {
key = p.currentName();
}
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);
}
if (value == null) {
_tryToAddNull(p, ctxt, cache, key);
continue;
}
cache.put(key, value);
}
return cache;
}
private void expect(JsonParser p, JsonToken token) throws IOException {
if (p.currentToken() != token) {
throw new JsonMappingException(p, "Expecting " + token + " to start `Cache` value, found " + p.currentToken(),
p.currentLocation());
}
}
/**
* Some/many Guava containers do not allow addition of {@code null} values,
* so isolate handling here.
*
* @since 2.17
*/
protected void _tryToAddNull(JsonParser p, DeserializationContext ctxt,
T cache, Object key)
throws IOException
{
// Ideally we'd have better idea of where nulls are accepted, but first
// let's just produce something better than NPE:
try {
cache.put(key, null);
} catch (NullPointerException e) {
ctxt.handleUnexpectedToken(_valueType, JsonToken.VALUE_NULL, p,
"Guava `Cache` of type %s does not accept `null` values",
ClassUtil.classNameOf(cache));
}
}
}