RangeSetSerializer.java

package tools.jackson.datatype.guava.ser;

import java.util.Set;

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.BeanProperty;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.impl.TypeWrappedSerializer;
import tools.jackson.databind.ser.std.StdSerializer;

import com.google.common.collect.ImmutableRangeSet;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;

public class RangeSetSerializer extends StdSerializer<RangeSet<Comparable<?>>>
{
    /**
     * Serializer for individual {@link Range} elements (wrapped with
     * type serializer, if {@code Range}s have type ids).
     */
    private final ValueSerializer<Object> _rangeSerializer;

    public RangeSetSerializer() {
        super(RangeSet.class);
        _rangeSerializer = null;
    }

    /**
     * @param rangeSer Serializer for individual {@link Range} elements
     *    (since 3.3; was serializer for {@code List} of ranges before)
     */
    @SuppressWarnings("unchecked")
    protected RangeSetSerializer(RangeSetSerializer base, ValueSerializer<?> rangeSer) {
        super(base);
        _rangeSerializer = (ValueSerializer<Object>) rangeSer;
    }

    @Override
    public void serialize(RangeSet<Comparable<?>> value, JsonGenerator g,
            SerializationContext ctxt)
        throws JacksonException
    {
        Set<Range<Comparable<?>>> ranges = value.asRanges();
        // Same as databind `CollectionSerializer`
        if ((ranges.size() == 1) && ctxt.isEnabled(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)) {
            g.assignCurrentValue(value);
            _writeRanges(ranges, g, ctxt);
            return;
        }
        g.writeStartArray(value, ranges.size());
        _writeRanges(ranges, g, ctxt);
        g.writeEndArray();
    }

    @Override
    public void serializeWithType(RangeSet<Comparable<?>> value, JsonGenerator g,
            SerializationContext ctxt, TypeSerializer typeSer)
        throws JacksonException
    {
        WritableTypeId typeIdDef = typeSer.writeTypePrefix(g, ctxt,
                typeSer.typeId(value, _typeIdClass(value), JsonToken.START_ARRAY));
        g.assignCurrentValue(value);
        _writeRanges(value.asRanges(), g, ctxt);
        typeSer.writeTypeSuffix(g, ctxt, typeIdDef);
    }

    private void _writeRanges(Set<Range<Comparable<?>>> ranges, JsonGenerator g,
            SerializationContext ctxt)
        throws JacksonException
    {
        if (_rangeSerializer == null) {
            ctxt.reportBadDefinition(handledType(), "Not contextualized to have value serializer");
        }
        for (Range<?> range : ranges) {
            _rangeSerializer.serialize(range, g, ctxt);
        }
    }

    // Deserializer always builds an `ImmutableRangeSet`, so other implementations
    // (`TreeRangeSet`, views like `complement()`) use the `RangeSet` interface as type id
    private static Class<?> _typeIdClass(RangeSet<?> value) {
        return (value instanceof ImmutableRangeSet<?>) ? value.getClass() : RangeSet.class;
    }

    @Override
    public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property)
    {
        JavaType rangeType = ctxt.constructType(Range.class);
        ValueSerializer<?> rangeSer = findAnnotatedContentSerializer(ctxt, property);
        rangeSer = findContextualConvertingSerializer(ctxt, property, rangeSer);
        if (rangeSer == null) {
            rangeSer = ctxt.findContentValueSerializer(rangeType, property);
        } else {
            rangeSer = ctxt.handleSecondaryContextualization(rangeSer, property);
        }
        TypeSerializer typeSer = ctxt.findTypeSerializer(rangeType);
        if (typeSer != null) {
            rangeSer = new TypeWrappedSerializer(typeSer.forProperty(ctxt, property), rangeSer);
        }
        return new RangeSetSerializer(this, rangeSer);
    }
}