Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suppport simple deserialization of Cache #116

Merged
merged 6 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referenced most from

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);
Comment on lines +137 to +150
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referenced most from...

Exception this part where we do not assume the value is array like

}
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