PrimitiveCollectionDeserializers.java

package tools.jackson.datatype.fastutil.deser;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Supplier;

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

import com.fasterxml.jackson.annotation.JsonFormat;

import tools.jackson.databind.BeanProperty;
import tools.jackson.databind.DeserializationConfig;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.deser.NullValueProvider;
import tools.jackson.databind.type.LogicalType;
import tools.jackson.databind.util.AccessPattern;
import tools.jackson.datatype.fastutil.util.ContentNulls;
import tools.jackson.datatype.fastutil.util.Merging;
import tools.jackson.datatype.fastutil.util.OptionalTypes;
import tools.jackson.datatype.primitive_collections_base.deser.BaseCharCollectionDeserializer;
import tools.jackson.datatype.primitive_collections_base.deser.BaseCollectionDeserializer;

import it.unimi.dsi.fastutil.booleans.*;
import it.unimi.dsi.fastutil.bytes.*;
import it.unimi.dsi.fastutil.chars.*;
import it.unimi.dsi.fastutil.doubles.*;
import it.unimi.dsi.fastutil.floats.*;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.shorts.*;

/**
 * Deserializers for fastutil collections of primitive values
 * ({@code IntCollection}, {@code LongList}, {@code DoubleSortedSet} and so on).
 */
public final class PrimitiveCollectionDeserializers
{
    private PrimitiveCollectionDeserializers() { }

    // Collection types of `fastutil-core` (a subset of `fastutil`) that are missing are
    // skipped: it has no `boolean` or `byte` collections, and only lists of `short`,
    // `char` and `float`
    private static final List<Support<?>> SUPPORTS = new ArrayList<>();

    static {
        _register(() -> new Support<>(new ContainerFamily<BooleanCollection>(BooleanCollection.class)
                .addIfPresent(f -> f.add(BooleanSet.class, BooleanOpenHashSet::new, BooleanArraySet::new))
                .addIfPresent(f -> f.add(BooleanBigList.class, BooleanBigArrayBigList::new, BooleanBigArrayBigList::new))
                .addIfPresent(f -> f.add(BooleanList.class, BooleanArrayList::new, BooleanArrayList::new))
                .addIfPresent(f -> f.add(BooleanCollection.class, BooleanArrayList::new, BooleanArrayList::new)),
                BooleanDeserializer::new));
        _register(() -> new Support<>(new ContainerFamily<ByteCollection>(ByteCollection.class)
                .addIfPresent(f -> f.add(ByteSortedSet.class, ByteRBTreeSet::new, ByteRBTreeSet::new))
                .addIfPresent(f -> f.add(ByteSet.class, ByteOpenHashSet::new, ByteLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(ByteBigList.class, ByteBigArrayBigList::new, ByteBigArrayBigList::new))
                .addIfPresent(f -> f.add(ByteList.class, ByteArrayList::new, ByteArrayList::new))
                .addIfPresent(f -> f.add(ByteCollection.class, ByteArrayList::new, ByteArrayList::new)),
                ByteDeserializer::new));
        _register(() -> new Support<>(new ContainerFamily<ShortCollection>(ShortCollection.class)
                .addIfPresent(f -> f.add(ShortSortedSet.class, ShortRBTreeSet::new, ShortRBTreeSet::new))
                .addIfPresent(f -> f.add(ShortSet.class, ShortOpenHashSet::new, ShortLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(ShortBigList.class, ShortBigArrayBigList::new, ShortBigArrayBigList::new))
                .addIfPresent(f -> f.add(ShortList.class, ShortArrayList::new, ShortArrayList::new))
                .addIfPresent(f -> f.add(ShortCollection.class, ShortArrayList::new, ShortArrayList::new)),
                ShortDeserializer::new));
        _register(() -> new Support<>(new ContainerFamily<CharCollection>(CharCollection.class)
                .addIfPresent(f -> f.add(CharSortedSet.class, CharRBTreeSet::new, CharRBTreeSet::new))
                .addIfPresent(f -> f.add(CharSet.class, CharOpenHashSet::new, CharLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(CharBigList.class, CharBigArrayBigList::new, CharBigArrayBigList::new))
                .addIfPresent(f -> f.add(CharList.class, CharArrayList::new, CharArrayList::new))
                .addIfPresent(f -> f.add(CharCollection.class, CharArrayList::new, CharArrayList::new)),
                (type, creator) -> new CharDeserializer(type.getRawClass(), creator)));
        _register(() -> new Support<>(new ContainerFamily<IntCollection>(IntCollection.class)
                .addIfPresent(f -> f.add(IntSortedSet.class, IntRBTreeSet::new, IntRBTreeSet::new))
                .addIfPresent(f -> f.add(IntSet.class, IntOpenHashSet::new, IntLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(IntBigList.class, IntBigArrayBigList::new, IntBigArrayBigList::new))
                .addIfPresent(f -> f.add(IntList.class, IntArrayList::new, IntArrayList::new))
                .addIfPresent(f -> f.add(IntCollection.class, IntArrayList::new, IntArrayList::new)),
                IntDeserializer::new));
        _register(() -> new Support<>(new ContainerFamily<LongCollection>(LongCollection.class)
                .addIfPresent(f -> f.add(LongSortedSet.class, LongRBTreeSet::new, LongRBTreeSet::new))
                .addIfPresent(f -> f.add(LongSet.class, LongOpenHashSet::new, LongLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(LongBigList.class, LongBigArrayBigList::new, LongBigArrayBigList::new))
                .addIfPresent(f -> f.add(LongList.class, LongArrayList::new, LongArrayList::new))
                .addIfPresent(f -> f.add(LongCollection.class, LongArrayList::new, LongArrayList::new)),
                LongDeserializer::new));
        _register(() -> new Support<>(new ContainerFamily<FloatCollection>(FloatCollection.class)
                .addIfPresent(f -> f.add(FloatSortedSet.class, FloatRBTreeSet::new, FloatRBTreeSet::new))
                .addIfPresent(f -> f.add(FloatSet.class, FloatOpenHashSet::new, FloatLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(FloatBigList.class, FloatBigArrayBigList::new, FloatBigArrayBigList::new))
                .addIfPresent(f -> f.add(FloatList.class, FloatArrayList::new, FloatArrayList::new))
                .addIfPresent(f -> f.add(FloatCollection.class, FloatArrayList::new, FloatArrayList::new)),
                FloatDeserializer::new));
        _register(() -> new Support<>(new ContainerFamily<DoubleCollection>(DoubleCollection.class)
                .addIfPresent(f -> f.add(DoubleSortedSet.class, DoubleRBTreeSet::new, DoubleRBTreeSet::new))
                .addIfPresent(f -> f.add(DoubleSet.class, DoubleOpenHashSet::new, DoubleLinkedOpenHashSet::new))
                .addIfPresent(f -> f.add(DoubleBigList.class, DoubleBigArrayBigList::new, DoubleBigArrayBigList::new))
                .addIfPresent(f -> f.add(DoubleList.class, DoubleArrayList::new, DoubleArrayList::new))
                .addIfPresent(f -> f.add(DoubleCollection.class, DoubleArrayList::new, DoubleArrayList::new)),
                DoubleDeserializer::new));
    }

    private static void _register(Supplier<Support<?>> support) {
        OptionalTypes.registerIfPresent(() -> SUPPORTS.add(support.get()));
    }

    /**
     * @return Whether given type is a fastutil collection of primitive values
     *    that can be constructed
     */
    public static boolean handles(Class<?> rawType) {
        for (Support<?> support : SUPPORTS) {
            if (support.family().handles(rawType)) {
                return support.family().findCreator(rawType) != null;
            }
        }
        return false;
    }

    /**
     * @param elementDeserializer Custom deserializer for elements, if any (like one
     *    specified with {@code @JsonDeserialize(contentUsing = ...)})
     *
     * @return Deserializer for given type if it is a fastutil collection of primitive
     *    values that can be constructed; {@code null} otherwise
     */
    public static ValueDeserializer<?> findDeserializer(DeserializationConfig config,
            JavaType type, ValueDeserializer<?> elementDeserializer)
    {
        for (Support<?> support : SUPPORTS) {
            if (support.family().handles(type.getRawClass())) {
                return support.findDeserializer(config, type, elementDeserializer);
            }
        }
        return null;
    }

    private record Support<C>(ContainerFamily<C> family,
            BiFunction<JavaType, ContainerFamily.Creator<C>, ValueDeserializer<?>> deserializerFactory)
    {
        ValueDeserializer<?> findDeserializer(DeserializationConfig config, JavaType type,
                ValueDeserializer<?> elementDeserializer)
        {
            ContainerFamily.Creator<C> creator = family.findCreator(type.getRawClass());
            if (creator == null) {
                return null;
            }
            if (elementDeserializer != null) {
                // Let standard Jackson handling use the custom deserializer (which
                // produces boxed values)
                // (handler needs to be set explicitly: resolved types are cached without one)
                return new BoxedDelegatingDeserializer<>(type, config.getTypeFactory()
                        .constructCollectionType(ArrayList.class, type.getContentType())
                        .withContentValueHandler(elementDeserializer), creator, false, true);
            }
            return deserializerFactory.apply(type, creator);
        }
    }

    /**
     * Base class for deserializers of primitive collections: elements are added
     * to an intermediate collection which is then converted to the result type.
     *<p>
     * Handles per-property settings the same way as {@code jackson-databind} does
     * for primitive arrays: {@code @JsonSetter(contentNulls = ...)} ({@code SKIP} and
     * {@code FAIL}; others handle {@code null}s as specified by
     * {@link tools.jackson.databind.DeserializationFeature#FAIL_ON_NULL_FOR_PRIMITIVES})
     * and {@code @JsonFormat(with = ACCEPT_SINGLE_VALUE_AS_ARRAY)}.
     */
    public abstract static class Base<C extends Collection<?>>
        extends BaseCollectionDeserializer<C, C>
    {
        protected final ContainerFamily.Creator<C> _creator;

        // Handler for JSON nulls as elements, if not the default one
        protected final NullValueProvider _nuller;

        // Per-property override for `ACCEPT_SINGLE_VALUE_AS_ARRAY`, if any
        protected final Boolean _unwrapSingle;

        protected Base(JavaType type, ContainerFamily.Creator<C> creator) {
            super(type);
            _creator = creator;
            _nuller = null;
            _unwrapSingle = null;
        }

        protected Base(Base<C> src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src.getValueType());
            _creator = src._creator;
            _nuller = nuller;
            _unwrapSingle = unwrapSingle;
        }

        protected abstract Base<C> withResolved(NullValueProvider nuller, Boolean unwrapSingle);

        @Override
        public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
        {
            Boolean unwrapSingle = findFormatFeature(ctxt, property, Collection.class,
                    JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
            NullValueProvider nuller = ContentNulls.findNuller(ctxt, property, getValueType());
            if (Objects.equals(unwrapSingle, _unwrapSingle) && (nuller == _nuller)) {
                return this;
            }
            return withResolved(nuller, unwrapSingle);
        }

        @SuppressWarnings("unchecked")
        @Override
        public C deserialize(JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            if (p.isExpectedStartArrayToken()) {
                return _deserializeContents(p, ctxt);
            }
            // Same as for primitive arrays by `jackson-databind`: Strings are handled
            // as such (empty and blank ones may be coerced, as configured) unless
            // single values are accepted; blank Strings even then
            final boolean canWrap = (_unwrapSingle == Boolean.TRUE) || ((_unwrapSingle == null)
                    && ctxt.isEnabled(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY));
            if (p.hasToken(JsonToken.VALUE_STRING)) {
                if (!canWrap || _isBlank(p.getString())) {
                    return _deserializeFromStringForContainer(p, ctxt);
                }
            }
            if (canWrap) {
                return _deserializeFromSingleValue(p, ctxt);
            }
            return (C) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
        }

        @Override
        protected C _deserializeContents(JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            C collection = createIntermediate(ctxt);
            _readContents(p, ctxt, collection);
            return finish(ctxt, collection);
        }

        // Reads elements of a JSON Array (parser pointing to START_ARRAY) into given collection
        private void _readContents(JsonParser p, DeserializationContext ctxt, C target)
            throws JacksonException
        {
            JsonToken t;
            while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
                if ((t == JsonToken.VALUE_NULL) && (_nuller != null)) {
                    _nuller.getNullValue(ctxt); // fails, or skips
                    continue;
                }
                add(target, p, ctxt);
            }
        }

        @Override
        protected C _deserializeFromSingleValue(JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            C collection = createIntermediate(ctxt);
            if (p.hasToken(JsonToken.VALUE_NULL) && (_nuller != null)) {
                _nuller.getNullValue(ctxt);
            } else {
                add(collection, p, ctxt);
            }
            return finish(ctxt, collection);
        }

        @Override
        protected C createIntermediate() {
            return _creator.create(null);
        }

        @Override
        protected C createIntermediate(DeserializationContext ctxt) throws JacksonException {
            return _creator.create(ctxt);
        }

        @Override
        protected C finish(C intermediate) {
            return _creator.finish(null, intermediate);
        }

        @Override
        protected C finish(DeserializationContext ctxt, C intermediate) throws JacksonException {
            return _creator.finish(ctxt, intermediate);
        }

        // Merging (`@JsonMerge`, `ObjectReader.readerForUpdating()`) adds elements:
        // directly for JSON Arrays; other input (single values, coercions) via a new collection
        @Override
        public C deserialize(JsonParser p, DeserializationContext ctxt, C intoValue)
            throws JacksonException
        {
            if (p.isExpectedStartArrayToken()) {
                try {
                    _readContents(p, ctxt, intoValue);
                } catch (UnsupportedOperationException e) {
                    throw Merging.failure(ctxt, intoValue, e);
                }
                return intoValue;
            }
            return Merging.addAll(ctxt, intoValue, deserialize(p, ctxt));
        }

        @Override
        public Boolean supportsUpdate(DeserializationConfig config) {
            return Boolean.TRUE;
        }

        @Override
        public Object getEmptyValue(DeserializationContext ctxt) {
            return _creator.createEmpty(ctxt);
        }

        @Override
        public AccessPattern getEmptyAccessPattern() {
            return AccessPattern.DYNAMIC;
        }

        @Override
        public LogicalType logicalType() {
            return LogicalType.Collection;
        }
    }

    public static final class BooleanDeserializer extends Base<BooleanCollection>
    {
        public BooleanDeserializer(JavaType type, ContainerFamily.Creator<BooleanCollection> creator) {
            super(type, creator);
        }

        private BooleanDeserializer(BooleanDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected BooleanDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new BooleanDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(BooleanCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseBooleanPrimitive(p, ctxt));
        }
    }

    public static final class ByteDeserializer extends Base<ByteCollection>
    {
        public ByteDeserializer(JavaType type, ContainerFamily.Creator<ByteCollection> creator) {
            super(type, creator);
        }

        private ByteDeserializer(ByteDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected ByteDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new ByteDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(ByteCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseBytePrimitive(p, ctxt));
        }
    }

    public static final class ShortDeserializer extends Base<ShortCollection>
    {
        public ShortDeserializer(JavaType type, ContainerFamily.Creator<ShortCollection> creator) {
            super(type, creator);
        }

        private ShortDeserializer(ShortDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected ShortDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new ShortDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(ShortCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseShortPrimitive(p, ctxt));
        }
    }

    public static final class IntDeserializer extends Base<IntCollection>
    {
        public IntDeserializer(JavaType type, ContainerFamily.Creator<IntCollection> creator) {
            super(type, creator);
        }

        private IntDeserializer(IntDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected IntDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new IntDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(IntCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseIntPrimitive(p, ctxt));
        }
    }

    public static final class LongDeserializer extends Base<LongCollection>
    {
        public LongDeserializer(JavaType type, ContainerFamily.Creator<LongCollection> creator) {
            super(type, creator);
        }

        private LongDeserializer(LongDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected LongDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new LongDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(LongCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseLongPrimitive(p, ctxt));
        }
    }

    public static final class FloatDeserializer extends Base<FloatCollection>
    {
        public FloatDeserializer(JavaType type, ContainerFamily.Creator<FloatCollection> creator) {
            super(type, creator);
        }

        private FloatDeserializer(FloatDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected FloatDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new FloatDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(FloatCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseFloatPrimitive(p, ctxt));
        }
    }

    public static final class DoubleDeserializer extends Base<DoubleCollection>
    {
        public DoubleDeserializer(JavaType type, ContainerFamily.Creator<DoubleCollection> creator) {
            super(type, creator);
        }

        private DoubleDeserializer(DoubleDeserializer src, NullValueProvider nuller, Boolean unwrapSingle) {
            super(src, nuller, unwrapSingle);
        }

        @Override
        protected DoubleDeserializer withResolved(NullValueProvider nuller, Boolean unwrapSingle) {
            return new DoubleDeserializer(this, nuller, unwrapSingle);
        }

        @Override
        protected void add(DoubleCollection intermediate, JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            intermediate.add(_parseDoublePrimitive(p, ctxt));
        }
    }

    /**
     * Char collections are read from a JSON String (as written by default), or from
     * a JSON Array of single-character Strings.
     */
    public static final class CharDeserializer
        extends BaseCharCollectionDeserializer<CharCollection, CharCollection>
    {
        private final ContainerFamily.Creator<CharCollection> _creator;

        // Handler for JSON nulls as elements of JSON Arrays, if not the default one
        private final NullValueProvider _nuller;

        public CharDeserializer(Class<?> rawType, ContainerFamily.Creator<CharCollection> creator) {
            this(rawType, creator, null);
        }

        private CharDeserializer(Class<?> rawType, ContainerFamily.Creator<CharCollection> creator,
                NullValueProvider nuller) {
            super(_cast(rawType));
            _creator = creator;
            _nuller = nuller;
        }

        @SuppressWarnings("unchecked")
        private static Class<? super CharCollection> _cast(Class<?> rawType) {
            return (Class<? super CharCollection>) rawType;
        }

        @Override
        public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
        {
            NullValueProvider nuller = ContentNulls.findNuller(ctxt, property,
                    ctxt.constructType(handledType()));
            if (nuller == _nuller) {
                return this;
            }
            return new CharDeserializer(handledType(), _creator, nuller);
        }

        @Override
        public CharCollection deserialize(JsonParser p, DeserializationContext ctxt)
            throws JacksonException
        {
            if (p.isExpectedStartArrayToken()) {
                CharCollection collection = createIntermediate(ctxt);
                _readArray(p, ctxt, collection);
                return finish(ctxt, collection);
            }
            if (!p.hasToken(JsonToken.VALUE_STRING)) {
                return (CharCollection) ctxt.handleUnexpectedToken(getValueType(ctxt), p);
            }
            return super.deserialize(p, ctxt);
        }

        // Handled here (not by base class) to support `contentNulls`, and to handle
        // other nulls as specified by `FAIL_ON_NULL_FOR_PRIMITIVES` (like for `char[]`)
        private void _readArray(JsonParser p, DeserializationContext ctxt, CharCollection collection)
            throws JacksonException
        {
            JsonToken t;
            while ((t = p.nextToken()) != JsonToken.END_ARRAY) {
                if (t == JsonToken.VALUE_NULL) {
                    if (_nuller != null) {
                        _nuller.getNullValue(ctxt); // fails, or skips
                        continue;
                    }
                    _verifyNullForPrimitive(ctxt);
                    collection.add('\0');
                    continue;
                }
                if (t != JsonToken.VALUE_STRING) {
                    // problem handler may also return `null`
                    Character c = (Character) ctxt.handleUnexpectedToken(Character.TYPE, p);
                    if (c == null) {
                        _verifyNullForPrimitive(ctxt);
                        c = '\0';
                    }
                    collection.add(c.charValue());
                    continue;
                }
                String str = p.getString();
                if (str.length() != 1) {
                    ctxt.reportInputMismatch(this,
                            "Cannot convert a JSON String of length %d into a char element of char array",
                            str.length());
                }
                collection.add(str.charAt(0));
            }
        }

        @Override
        protected CharCollection createIntermediate() {
            return _creator.create(null);
        }

        @Override
        protected CharCollection createIntermediate(DeserializationContext ctxt) throws JacksonException {
            return _creator.create(ctxt);
        }

        @Override
        protected CharCollection finish(CharCollection intermediate) {
            return _creator.finish(null, intermediate);
        }

        @Override
        protected CharCollection finish(DeserializationContext ctxt, CharCollection intermediate)
            throws JacksonException
        {
            return _creator.finish(ctxt, intermediate);
        }

        @Override
        public CharCollection deserialize(JsonParser p, DeserializationContext ctxt,
                CharCollection intoValue)
            throws JacksonException
        {
            // Merging adds elements directly (from a JSON Array or String)
            try {
                if (p.isExpectedStartArrayToken()) {
                    _readArray(p, ctxt, intoValue);
                    return intoValue;
                }
                if (p.hasToken(JsonToken.VALUE_STRING)) {
                    addAll(intoValue, p.getStringCharacters(), p.getStringOffset(), p.getStringLength());
                    return intoValue;
                }
            } catch (UnsupportedOperationException e) {
                throw Merging.failure(ctxt, intoValue, e);
            }
            return Merging.addAll(ctxt, intoValue, deserialize(p, ctxt));
        }

        @Override
        public Boolean supportsUpdate(DeserializationConfig config) {
            return Boolean.TRUE;
        }

        @Override
        public Object getEmptyValue(DeserializationContext ctxt) {
            return _creator.createEmpty(ctxt);
        }

        @Override
        public AccessPattern getEmptyAccessPattern() {
            return AccessPattern.DYNAMIC;
        }

        @Override
        protected void add(CharCollection intermediate, char c) {
            intermediate.add(c);
        }

        @Override
        protected void addAll(CharCollection intermediate, char[] chars, int off, int len) {
            for (int i = off, end = off + len; i < end; ++i) {
                intermediate.add(chars[i]);
            }
        }
    }
}