GuavaImmutableCollectionDeserializer.java

package tools.jackson.datatype.guava.deser;

import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;

import tools.jackson.databind.DeserializationConfig;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.deser.NullValueProvider;
import tools.jackson.databind.jsontype.TypeDeserializer;
import tools.jackson.databind.util.AccessPattern;
import tools.jackson.databind.util.ClassUtil;

import com.google.common.collect.ImmutableCollection;

abstract class GuavaImmutableCollectionDeserializer<T extends ImmutableCollection<Object>>
        extends GuavaCollectionDeserializer<T>
{
    GuavaImmutableCollectionDeserializer(JavaType selfType,
            ValueDeserializer<?> deser, TypeDeserializer typeDeser,
            NullValueProvider nuller, Boolean unwrapSingle) {
        super(selfType, deser, typeDeser, nuller, unwrapSingle);
    }

    protected abstract ImmutableCollection.Builder<Object> createBuilder();

    // Can not modify Immutable collections now can we
    @Override
    public Boolean supportsUpdate(DeserializationConfig config) {
        return Boolean.FALSE;
    }

    @Override
    public AccessPattern getEmptyAccessPattern() {
        // But we should be able to just share immutable empty instance
        return AccessPattern.CONSTANT;
    }

    @Override
    public T getEmptyValue(DeserializationContext ctxt) {
        return _createEmpty(ctxt);
    }

    @Override
    protected T _deserializeContents(JsonParser p, DeserializationContext ctxt)
        throws JacksonException
    {
        // Builder only constructed once there are 2 elements (or a `null` one)
        Object first = null;
        ImmutableCollection.Builder<Object> builder = null;

        JsonToken t;
        while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
            Object value = _deserializeElement(p, ctxt, t);
            if (value == SKIPPED_ELEMENT) {
                continue;
            }
            if (builder == null) {
                if ((first == null) && (value != null)) {
                    first = value;
                    continue;
                }
                builder = createBuilder();
                if (first != null) {
                    _add(ctxt, builder, first);
                }
            }
            _addToBuilder(p, ctxt, builder, value);
        }

        if (builder != null) {
            return _build(ctxt, builder);
        }
        if (first == null) {
            return _createEmpty(ctxt);
        }
        return _createWithSingleElement(ctxt, first);
    }

    private void _addToBuilder(JsonParser p, DeserializationContext ctxt,
            ImmutableCollection.Builder<Object> builder, Object value)
        throws JacksonException
    {
        // Avoid lambda allocation for the common non-null case
        if (value == null) {
            _addElement(p, ctxt, p.currentToken(), null, v -> _add(ctxt, builder, v));
        } else {
            _add(ctxt, builder, value);
        }
    }

    private T _build(DeserializationContext ctxt, ImmutableCollection.Builder<Object> builder)
        throws JacksonException
    {
        // No class outside of the package will be able to subclass us,
        // and we provide the proper builder for the subclasses we implement.
        try {
            @SuppressWarnings("unchecked")
            T collection = (T) builder.build();
            return collection;
        } catch (ClassCastException e) {
            // Sorted builders fail this way if elements are not mutually comparable
            return _reportBuildFailure(ctxt, e.getMessage());
        }
    }

    private ImmutableCollection.Builder<Object> _add(DeserializationContext ctxt,
            ImmutableCollection.Builder<Object> builder, Object value)
        throws JacksonException
    {
        _verifyComparable(ctxt, value);
        try {
            builder.add(value);
        } catch (ClassCastException e) {
            // Sorted builders may sort (and fail) while elements are still being added
            _reportBuildFailure(ctxt, e.getMessage());
        }
        return builder;
    }

    /**
     * Helper method sub-classes can use for implementing
     * {@link #_createWithSingleElement}, to get the same error handling as
     * for multi-element content.
     */
    protected T _buildWithSingleElement(DeserializationContext ctxt, Object value)
        throws JacksonException
    {
        // NOTE: `null` value fails with NPE, handled by caller
        return _build(ctxt, _add(ctxt, createBuilder(), value));
    }

    /**
     * Overridable method that sorted containers override to return {@code true}:
     * if so, all (non-null) elements are verified to be {@link Comparable}.
     * This is needed since sorted builders never compare a single element.
     */
    protected boolean _requiresComparableElements() {
        return false;
    }

    private void _verifyComparable(DeserializationContext ctxt, Object value)
        throws JacksonException
    {
        if (_requiresComparableElements()
                && (value != null) && !(value instanceof Comparable<?>)) {
            _reportBuildFailure(ctxt, String.format("element of type %s is not `Comparable`",
                    ClassUtil.classNameOf(value)));
        }
    }
}