CacheSerializer.java
package tools.jackson.datatype.guava.ser;
import java.util.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonToken;
import tools.jackson.core.type.WritableTypeId;
import tools.jackson.databind.*;
import tools.jackson.databind.introspect.AnnotatedMember;
import tools.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitable;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsonFormatVisitors.JsonMapFormatVisitor;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.PropertyFilter;
import tools.jackson.databind.ser.impl.PropertySerializerMap;
import tools.jackson.databind.ser.jdk.MapProperty;
import tools.jackson.databind.ser.std.StdContainerSerializer;
import tools.jackson.databind.type.MapLikeType;
import com.google.common.cache.Cache;
/**
* Serializer for Guava's {@link Cache} values.
*/
public class CacheSerializer
extends StdContainerSerializer<Cache<?, ?>>
{
private final MapLikeType _type;
private final BeanProperty _property;
private final ValueSerializer<Object> _keySerializer;
private final TypeSerializer _valueTypeSerializer;
private final ValueSerializer<Object> _valueSerializer;
/**
* Set of entries to omit during serialization, if any
*/
protected final Set<String> _ignoredEntries;
/**
* The id of the property filter to use, if any; null if none.
*/
protected final Object _filterId;
/**
* Flag set if output is forced to be sorted by keys (usually due to annotation).
*/
protected final boolean _sortKeys;
/**
* If value type can not be statically determined, mapping from
* runtime value types to serializers are stored in this object.
*/
protected PropertySerializerMap _dynamicValueSerializers;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public CacheSerializer(MapLikeType type, BeanDescription.Supplier beanDescRef,
ValueSerializer<Object> keySerializer, TypeSerializer vts,
ValueSerializer<Object> valueSerializer,
Set<String> ignoredEntries, Object filterId)
{
super(type, null);
_type = type;
_property = null;
_keySerializer = keySerializer;
_valueTypeSerializer = vts;
_valueSerializer = valueSerializer;
_ignoredEntries = ignoredEntries;
_filterId = filterId;
_sortKeys = false;
_dynamicValueSerializers = PropertySerializerMap.emptyForProperties();
}
@SuppressWarnings("unchecked")
protected CacheSerializer(CacheSerializer src, BeanProperty property,
ValueSerializer<?> keySerializer, TypeSerializer vts, ValueSerializer<?> valueSerializer,
Set<String> ignoredEntries, Object filterId, boolean sortKeys)
{
super(src);
_type = src._type;
_property = property;
_keySerializer = (ValueSerializer<Object>) keySerializer;
_valueTypeSerializer = vts;
_valueSerializer = (ValueSerializer<Object>) valueSerializer;
_dynamicValueSerializers = src._dynamicValueSerializers;
_ignoredEntries = ignoredEntries;
_filterId = filterId;
_sortKeys = sortKeys;
}
protected CacheSerializer withResolved(BeanProperty property,
ValueSerializer<?> keySer, TypeSerializer vts, ValueSerializer<?> valueSer,
Set<String> ignored, Object filterId, boolean sortKeys)
{
return new CacheSerializer(this, property, keySer, vts, valueSer,
ignored, filterId, sortKeys);
}
@Override
protected StdContainerSerializer<?> _withValueTypeSerializer(TypeSerializer typeSer) {
return new CacheSerializer(this, _property, _keySerializer,
typeSer, _valueSerializer, _ignoredEntries, _filterId, _sortKeys);
}
/*
/**********************************************************
/* Post-processing (contextualization)
/**********************************************************
*/
@Override
public ValueSerializer<?> createContextual(SerializationContext ctxt,
BeanProperty property)
throws JacksonException
{
ValueSerializer<?> valueSer = _valueSerializer;
if (valueSer == null) { // if type is final, can actually resolve:
JavaType valueType = _type.getContentType();
if (valueType.isFinal()) {
valueSer = ctxt.findContentValueSerializer(valueType, property);
}
} else {
valueSer = valueSer.createContextual(ctxt, property);
}
final SerializationConfig config = ctxt.getConfig();
final AnnotationIntrospector intr = config.getAnnotationIntrospector();
final AnnotatedMember propertyAcc = (property == null) ? null : property.getMember();
ValueSerializer<?> keySer = null;
Object filterId = _filterId;
// First: if we have a property, may have property-annotation overrides
if (propertyAcc != null && intr != null) {
Object serDef = intr.findKeySerializer(config, propertyAcc);
if (serDef != null) {
keySer = ctxt.serializerInstance(propertyAcc, serDef);
}
serDef = intr.findContentSerializer(config, propertyAcc);
if (serDef != null) {
valueSer = ctxt.serializerInstance(propertyAcc, serDef);
}
filterId = intr.findFilterId(config, propertyAcc);
}
if (valueSer == null) {
valueSer = _valueSerializer;
}
// [datatype-guava#124]: May have a content converter
valueSer = findContextualConvertingSerializer(ctxt, property, valueSer);
if (valueSer == null) {
// One more thing -- if explicit content type is annotated,
// we can consider it a static case as well.
JavaType valueType = _type.getContentType();
if (valueType.useStaticType()) {
valueSer = ctxt.findContentValueSerializer(valueType, property);
}
} else {
valueSer = ctxt.handleSecondaryContextualization(valueSer, property);
}
if (keySer == null) {
keySer = _keySerializer;
}
if (keySer == null) {
keySer = ctxt.findKeySerializer(_type.getKeyType(), property);
} else {
keySer = ctxt.handleSecondaryContextualization(keySer, property);
}
// finally, TypeSerializers may need contextualization as well
TypeSerializer typeSer = _valueTypeSerializer;
if (typeSer != null) {
typeSer = typeSer.forProperty(ctxt, property);
}
Set<String> ignored = _ignoredEntries;
boolean sortKeys = false;
if (intr != null && propertyAcc != null) {
JsonIgnoreProperties.Value ignorals = intr.findPropertyIgnoralByName(ctxt.getConfig(), propertyAcc);
if (ignorals != null) {
Set<String> newIgnored = ignorals.findIgnoredForSerialization();
if ((newIgnored != null) && !newIgnored.isEmpty()) {
ignored = (ignored == null) ? new HashSet<String>() : new HashSet<>(ignored);
for (String str : newIgnored) {
ignored.add(str);
}
}
}
Boolean b = intr.findSerializationSortAlphabetically(config, propertyAcc);
sortKeys = (b != null) && b.booleanValue();
}
// 19-May-2016, tatu: Also check per-property format features, even if
// this isn't yet used (as per [guava#7])
JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
if (format != null) {
Boolean B = format.getFeature(JsonFormat.Feature.WRITE_SORTED_MAP_ENTRIES);
if (B != null) {
sortKeys = B.booleanValue();
}
}
return withResolved(property, keySer, typeSer, valueSer,
ignored, filterId, sortKeys);
}
/*
/**********************************************************
/* Accessors for ContainerSerializer
/**********************************************************
*/
@Override
public ValueSerializer<?> getContentSerializer() {
return _valueSerializer;
}
@Override
public JavaType getContentType() {
return _type.getContentType();
}
@Override
public boolean hasSingleElement(Cache<?, ?> cache) {
return cache.size() == 1;
}
@Override
public boolean isEmpty(SerializationContext ctxt, Cache<?, ?> value) {
return value.size() == 0;
}
/*
/**********************************************************
/* Abstract method implementations
/**********************************************************
*/
@Override
public void serialize(Cache<?, ?> value, JsonGenerator gen, SerializationContext ctxt)
throws JacksonException
{
gen.writeStartObject(value);
_writeContents(value, gen, ctxt);
gen.writeEndObject();
}
@Override
public void serializeWithType(Cache<?, ?> value, JsonGenerator gen, SerializationContext ctxt,
TypeSerializer typeSer)
throws JacksonException
{
gen.assignCurrentValue(value);
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, ctxt,
typeSer.typeId(value, JsonToken.START_OBJECT));
_writeContents(value, gen, ctxt);
typeSer.writeTypeSuffix(gen, ctxt, typeIdDef);
}
/*
/**********************************************************
/* Internal helper methods
/**********************************************************
*/
protected void _writeContents(Cache<?, ?> cache, JsonGenerator gen, SerializationContext ctxt)
throws JacksonException
{
Map<?, ?> value = cache.asMap();
if (!value.isEmpty()) {
if (_sortKeys || ctxt.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)) {
value = _orderEntriesByKey(value, gen, ctxt);
}
if (_filterId != null) {
serializeFilteredFields(value, gen, ctxt);
} else {
serializeFields(value, gen, ctxt);
}
}
}
private void serializeFields(Map<?, ?> mmap, JsonGenerator
gen, SerializationContext ctxt)
throws JacksonException
{
final Set<String> ignored = _ignoredEntries;
PropertySerializerMap serializers = _dynamicValueSerializers;
for (Map.Entry<?, ?> entry : mmap.entrySet()) {
// First, serialize key
Object key = entry.getKey();
if ((ignored != null) && ignored.contains(key)) {
continue;
}
if (key == null) {
ctxt.findNullKeySerializer(_type.getKeyType(), _property)
.serialize(null, gen, ctxt);
} else {
_keySerializer.serialize(key, gen, ctxt);
}
// Second, serialize value
ValueSerializer<Object> valueSer;
Object vv = entry.getValue();
// is this even possible?
if (vv == null) {
valueSer = ctxt.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
if (valueSer == null) {
Class<?> cc = vv.getClass();
valueSer = serializers.serializerFor(cc);
if (valueSer == null) {
valueSer = _findAndAddDynamic(serializers, cc, ctxt);
serializers = _dynamicValueSerializers;
}
}
}
if (_valueTypeSerializer == null) {
valueSer.serialize(vv, gen, ctxt);
} else {
valueSer.serializeWithType(vv, gen, ctxt, _valueTypeSerializer);
}
}
}
private void serializeFilteredFields(Map<?, ?> map, JsonGenerator gen, SerializationContext ctxt)
throws JacksonException
{
final Set<String> ignored = _ignoredEntries;
PropertyFilter filter = findPropertyFilter(ctxt, _filterId, map);
final MapProperty prop = new MapProperty(_valueTypeSerializer, _property);
for (Map.Entry<?, ?> entry : map.entrySet()) {
// First, serialize key
Object key = entry.getKey();
if ((ignored != null) && ignored.contains(key)) {
continue;
}
Object value = entry.getValue();
ValueSerializer<Object> valueSer;
if (value == null) {
// !!! TODO: null suppression?
valueSer = ctxt.getDefaultNullValueSerializer();
} else {
valueSer = _valueSerializer;
}
prop.reset(key, value, _keySerializer, valueSer);
try {
filter.serializeAsProperty(map, gen, ctxt, prop);
} catch (Exception e) {
String keyDesc = "" + key;
wrapAndThrow(ctxt, e, value, keyDesc);
}
}
}
/*
/**********************************************************
/* Schema related functionality
/**********************************************************
*/
@Override
public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
throws JacksonException
{
JsonMapFormatVisitor v2 = (visitor == null) ? null : visitor.expectMapFormat(typeHint);
if (v2 != null) {
v2.keyFormat(_keySerializer, _type.getKeyType());
ValueSerializer<?> valueSer = _valueSerializer;
final JavaType vt = _type.getContentType();
final SerializationContext ctxt = visitor.getContext();
if (valueSer == null) {
valueSer = _findAndAddDynamic(_dynamicValueSerializers, vt, ctxt);
}
final ValueSerializer<?> valueSer2 = valueSer;
v2.valueFormat(new JsonFormatVisitable() {
final JavaType arrayType = ctxt.getTypeFactory().constructArrayType(vt);
@Override
public void acceptJsonFormatVisitor(
JsonFormatVisitorWrapper v3, JavaType hint3)
throws JacksonException
{
JsonArrayFormatVisitor v4 = v3.expectArrayFormat(arrayType);
if (v4 != null) {
v4.itemsFormat(valueSer2, vt);
}
}
}, vt);
}
}
/*
/**********************************************************
/* Internal helper methods
/**********************************************************
*/
protected Map<?,?> _orderEntriesByKey(Map<?,?> value, JsonGenerator gen, SerializationContext ctxt)
throws JacksonException
{
try {
return new TreeMap<Object,Object>(value);
} catch (ClassCastException e) {
// Either key or value type not Comparable?
// 20-Mar-2023, tatu: Should we actually wrap & propagate failure or... ?
return value;
} catch (NullPointerException e) {
// Most likely null key that TreeMultimap won't accept. So... ?
ctxt.reportMappingProblem("Failed to sort Multimap entries due to `NullPointerException`: `null` key?");
return null;
}
}
protected final ValueSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
Class<?> type, SerializationContext ctxt)
throws JacksonException
{
PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, ctxt, _property);
// did we get a new map of serializers? If so, start using it
if (map != result.map) {
_dynamicValueSerializers = result.map;
}
return result.serializer;
}
protected final ValueSerializer<Object> _findAndAddDynamic(PropertySerializerMap map,
JavaType type, SerializationContext ctxt)
throws JacksonException
{
PropertySerializerMap.SerializerAndMapResult result = map.findAndAddSecondarySerializer(type, ctxt, _property);
if (map != result.map) {
_dynamicValueSerializers = result.map;
}
return result.serializer;
}
}