Skip to content

Commit

Permalink
[Guava] Suppport simple deserialization of Cache (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
JooHyukKim committed May 4, 2023
1 parent 2a8bd8c commit a1bbc87
Show file tree
Hide file tree
Showing 4 changed files with 432 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.fasterxml.jackson.datatype.guava;

import com.google.common.base.Optional;
import com.google.common.cache.Cache;
import com.google.common.cache.ForwardingCache;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.*;
import com.google.common.hash.HashCode;
import com.google.common.net.HostAndPort;
Expand Down Expand Up @@ -260,10 +263,42 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
if (Table.class.isAssignableFrom(raw)) {
// !!! TODO
}
// @since 2.16 : support Cache deserialization
java.util.Optional<JsonDeserializer<?>> cacheDeserializer = findCacheDeserializer(raw, type, config,
beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer);
if (cacheDeserializer.isPresent()) {
return cacheDeserializer.get();
}

return null;
}

/**
* Find matching implementation of {@link Cache} deserializers by checking
* if the parameter {@code raw} type is assignable.
*
* NOTE: Make sure the cache implementations are checked in such a way that more concrete classes are
* compared first before more abstract ones.
*
* @return An optional {@link JsonDeserializer} for the cache type, if found.
* @since 2.16
*/
private java.util.Optional<JsonDeserializer<?>> findCacheDeserializer(Class<?> raw, MapLikeType type,
DeserializationConfig config, BeanDescription beanDesc, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
{
/* // Example implementations
if (LoadingCache.class.isAssignableFrom(raw)) {
return ....your implementation....;
}
*/
if (Cache.class.isAssignableFrom(raw)) {
return java.util.Optional.of(
new SimpleCacheDeserializer(type, keyDeserializer, elementTypeDeserializer, elementDeserializer));
}
return java.util.Optional.empty();
}

@Override // since 2.7
public JsonDeserializer<?> findReferenceDeserializer(ReferenceType refType,
DeserializationConfig config, BeanDescription beanDesc,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.fasterxml.jackson.datatype.guava;

import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.deser.NullValueProvider;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.type.MapLikeType;
import com.fasterxml.jackson.datatype.guava.deser.cache.GuavaCacheDeserializer;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

/**
* {@link GuavaCacheDeserializer} class implementation for deserializing Guava {@link Cache} instances.
*
* @since 2.16
*/
public class SimpleCacheDeserializer
extends GuavaCacheDeserializer<Cache<Object,Object>>
{

/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/

public SimpleCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)
{
super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer);
}

public SimpleCacheDeserializer(MapLikeType type, KeyDeserializer keyDeserializer,
TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer,
NullValueProvider nvp)
{
super(type, keyDeserializer, elementTypeDeserializer, elementDeserializer, nvp);
}

/*
/**********************************************************************
/* Abstract method overrides
/**********************************************************************
*/

@Override
protected Cache<Object, Object> createCache() {
return CacheBuilder.newBuilder().build();
}

@Override
protected JsonDeserializer<?> _createContextual(MapLikeType t,
KeyDeserializer kd, TypeDeserializer vtd, JsonDeserializer<?> vd, NullValueProvider np)
{
return new SimpleCacheDeserializer(t, kd, vtd, vd, np);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package com.fasterxml.jackson.datatype.guava.deser.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.google.common.cache.Cache;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

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);
}
cache.put(key, value);
}
return cache;
}

private void expect(JsonParser p, JsonToken token) throws IOException {
if (p.getCurrentToken() != token) {
throw new JsonMappingException(p, "Expecting " + token + " to start `Cache` value, found " + p.currentToken(),
p.getCurrentLocation());
}
}
}
Loading

0 comments on commit a1bbc87

Please sign in to comment.