StdDelegatingSerializer.java

package tools.jackson.databind.ser.std;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.*;
import tools.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.impl.PropertySerializerMap;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.databind.util.Converter;

/**
 * Serializer implementation where given Java type is first converted
 * to an intermediate "delegate type" (using a configured
 * {@link Converter}, and then this delegate value is serialized by Jackson.
 *<p>
 * Note that although types may be related, they must not be same; trying
 * to do this will result in an exception.
 */
public class StdDelegatingSerializer
    extends StdSerializer<Object>
{
    // @since 3.0
    protected final BeanProperty _property;

    protected final Converter<Object,?> _converter;

    /**
     * Fully resolved delegate type, with generic information if any available.
     */
    protected final JavaType _delegateType;

    /**
     * Underlying serializer for type <code>T</code>.
     */
    protected final ValueSerializer<Object> _delegateSerializer;

    /**
     * If delegate serializer needs to be accessed dynamically (non-final
     * type, static type not forced), this data structure helps with efficient
     * lookups.
     *
     * @since 3.0
     */
    protected PropertySerializerMap _dynamicValueSerializers = PropertySerializerMap.emptyForProperties();

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

    @SuppressWarnings("unchecked")
    public StdDelegatingSerializer(Converter<?,?> converter)
    {
        super(Object.class);
        _converter = (Converter<Object,?>)converter;
        _delegateType = null;
        _delegateSerializer = null;
        _property = null;
    }

    @SuppressWarnings("unchecked")
    public <T> StdDelegatingSerializer(Class<T> cls, Converter<T,?> converter)
    {
        super(cls);
        _converter = (Converter<Object,?>)converter;
        _delegateType = null;
        _delegateSerializer = null;
        _property = null;
    }

    /**
     * @since 3.0
     */
    @SuppressWarnings("unchecked")
    public StdDelegatingSerializer(Converter<Object,?> converter,
            JavaType delegateType, ValueSerializer<?> delegateSerializer,
            BeanProperty prop)
    {
        super(delegateType);
        _converter = converter;
        _delegateType = delegateType;
        _delegateSerializer = (ValueSerializer<Object>) delegateSerializer;
        _property = prop;
    }

    /**
     * Method used for creating resolved contextual instances. Must be
     * overridden when sub-classing.
     */
    protected StdDelegatingSerializer withDelegate(Converter<Object,?> converter,
            JavaType delegateType, ValueSerializer<?> delegateSerializer,
            BeanProperty prop)
    {
        ClassUtil.verifyMustOverride(StdDelegatingSerializer.class, this, "withDelegate");
        return new StdDelegatingSerializer(converter, delegateType, delegateSerializer, prop);
    }

    /*
    /**********************************************************************
    /* Contextualization
    /**********************************************************************
     */

    @Override
    public void resolve(SerializationContext ctxt)
    {
        if (_delegateSerializer != null) {
            _delegateSerializer.resolve(ctxt);
        }
    }

    @Override
    public ValueSerializer<?> createContextual(SerializationContext ctxt, BeanProperty property)
    {
        ValueSerializer<?> delSer = _delegateSerializer;
        JavaType delegateType = _delegateType;

        if (delSer == null) {
            // Otherwise, need to locate serializer to delegate to. For that we need type information...
            if (delegateType == null) {
                delegateType = _converter.getOutputType(ctxt.getTypeFactory());
            }
            // 02-Apr-2015, tatu: For "dynamic case", where type is only specified as
            //    java.lang.Object (or missing generic), [databind#731]
            if (!delegateType.isJavaLangObject()) {
                delSer = ctxt.findValueSerializer(delegateType);
            }
        }
        if (delSer != null) {
            delSer = ctxt.handleSecondaryContextualization(delSer, property);
        }
        if ((delSer == _delegateSerializer)
                && (delegateType == _delegateType) && (property == _property)) {
            return this;
        }
        return withDelegate(_converter, delegateType, delSer, property);
    }

    /*
    /**********************************************************************
    /* Accessors
    /**********************************************************************
     */

    public Converter<Object, ?> getConverter() {
        return _converter;
    }

    @Override
    public ValueSerializer<?> getDelegatee() {
        return _delegateSerializer;
    }

    /*
    /**********************************************************************
    /* Serialization
    /**********************************************************************
     */

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializationContext ctxt)
        throws JacksonException
    {
        Object delegateValue = convertValue(ctxt, value);
        // should we accept nulls?
        if (delegateValue == null) {
            ctxt.defaultSerializeNullValue(gen);
            return;
        }
        // 02-Apr-2015, tatu: As per [databind#731] may need to do dynamic lookup
        ValueSerializer<Object> ser = _delegateSerializer;
        if (ser == null) {
            ser = _findSerializer(delegateValue, ctxt);
        }
        ser.serialize(delegateValue, gen, ctxt);
    }

    @Override
    public void serializeWithType(Object value, JsonGenerator gen, SerializationContext ctxt,
            TypeSerializer typeSer)
        throws JacksonException
    {
        // 03-Oct-2012, tatu: This is actually unlikely to work ok... but for now,
        //    let's give it a chance?
        Object delegateValue = convertValue(ctxt, value);
        // consider null (to be consistent with serialize method above)
        if (delegateValue == null) {
            ctxt.defaultSerializeNullValue(gen);
            return;
        }
        ValueSerializer<Object> ser = _delegateSerializer;
        if (ser == null) {
            ser = _findSerializer(delegateValue, ctxt);
        }
        ser.serializeWithType(delegateValue, gen, ctxt, typeSer);
    }

    @Override
    public boolean isEmpty(SerializationContext ctxt, Object value)
    {
        Object delegateValue = convertValue(ctxt, value);
        if (delegateValue == null) {
            return true;
        }
        ValueSerializer<Object> ser = _delegateSerializer;
        if (ser == null) {
            ser = _findSerializer(value, ctxt);
        }
        return ser.isEmpty(ctxt, delegateValue);
    }

    /*
    /**********************************************************************
    /* Schema functionality
    /**********************************************************************
     */

    @Override
    public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
    {
        // 03-Sep-2012, tatu: Not sure if this can be made to really work
        //    properly... but for now, try this:

        // 02-Apr-2015, tatu: For dynamic case, very little we can do
        if (_delegateSerializer != null) {
            _delegateSerializer.acceptJsonFormatVisitor(visitor, typeHint);
        }
    }

    /*
    /**********************************************************************
    /* Overridable methods
    /**********************************************************************
     */

    /**
     * Method called to convert from source Java value into delegate
     * value (which will be serialized using standard Jackson serializer for delegate type)
     *<P>
     * The default implementation uses configured {@link Converter} to do
     * conversion.
     *
     * @param value Value to convert
     *
     * @return Result of conversion
     */
    protected Object convertValue(SerializationContext ctxt,Object value) {
        return _converter.convert(ctxt, value);
    }

    /**
     * Helper method used for locating serializer to use in dynamic use case, where
     * actual type value gets converted to is not specified beyond basic
     * {@link java.lang.Object}, and where serializer needs to be located dynamically
     * based on actual value type.
     */
    protected ValueSerializer<Object> _findSerializer(Object value, SerializationContext ctxt)
    {
        // 17-Apr-2018, tatu: Basically inline `_findAndAddDynamic(...)`
        // 17-Apr-2018, tatu: difficult to know if these are primary or secondary serializers...
        Class<?> cc = value.getClass();
        PropertySerializerMap.SerializerAndMapResult result = _dynamicValueSerializers.findAndAddSecondarySerializer(cc,
                ctxt, _property);
        if (_dynamicValueSerializers != result.map) {
            _dynamicValueSerializers = result.map;
        }
        return result.serializer;
    }
}