BasicSerializerFactory.java

package tools.jackson.databind.ser;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.cfg.SerializerFactoryConfig;
import tools.jackson.databind.ext.OptionalHandlerFactory;
import tools.jackson.databind.ext.jdk8.*;
import tools.jackson.databind.introspect.*;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.jackson.JacksonSerializableSerializer;
import tools.jackson.databind.ser.jackson.JsonValueSerializer;
import tools.jackson.databind.ser.jdk.*;
import tools.jackson.databind.ser.std.*;
import tools.jackson.databind.type.*;
import tools.jackson.databind.util.*;

/**
 * Factory class that can provide serializers for standard JDK classes,
 * as well as custom classes that extend standard classes or implement
 * one of "well-known" interfaces (such as {@link java.util.Collection}).
 *<p>
 * Since all the serializers are eagerly instantiated, and there is
 * no additional introspection or customizability of these types,
 * this factory is essentially stateless.
 */
@SuppressWarnings("serial")
public abstract class BasicSerializerFactory
    extends SerializerFactory
    implements java.io.Serializable
{
    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    /**
     * Configuration settings for this factory; immutable instance (just like this
     * factory), new version created via copy-constructor (fluent-style)
     */
    protected final SerializerFactoryConfig _factoryConfig;

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

    /**
     * We will provide default constructor to allow sub-classing,
     * but make it protected so that no non-singleton instances of
     * the class will be instantiated.
     */
    protected BasicSerializerFactory(SerializerFactoryConfig config) {
        _factoryConfig = (config == null) ? new SerializerFactoryConfig() : config;
    }

    /**
     * Method used for creating a new instance of this factory, but with different
     * configuration. Reason for specifying factory method (instead of plain constructor)
     * is to allow proper sub-classing of factories.
     *<p>
     * Note that custom sub-classes generally <b>must override</b> implementation
     * of this method, as it usually requires instantiating a new instance of
     * factory type. Check out javadocs for
     * {@link tools.jackson.databind.ser.BeanSerializerFactory} for more details.
     */
    protected abstract SerializerFactory withConfig(SerializerFactoryConfig config);

    /**
     * Convenience method for creating a new factory instance with an additional
     * serializer provider.
     */
    @Override
    public final SerializerFactory withAdditionalSerializers(Serializers additional) {
        return withConfig(_factoryConfig.withAdditionalSerializers(additional));
    }

    /**
     * Convenience method for creating a new factory instance with an additional
     * key serializer provider.
     */
    @Override
    public final SerializerFactory withAdditionalKeySerializers(Serializers additional) {
        return withConfig(_factoryConfig.withAdditionalKeySerializers(additional));
    }

    /**
     * Convenience method for creating a new factory instance with additional bean
     * serializer modifier.
     */
    @Override
    public final SerializerFactory withSerializerModifier(ValueSerializerModifier modifier) {
        return withConfig(_factoryConfig.withSerializerModifier(modifier));
    }

    @Override
    public final SerializerFactory withNullValueSerializer(ValueSerializer<?> nvs) {
        return withConfig(_factoryConfig.withNullValueSerializer(nvs));
    }

    @Override
    public final SerializerFactory withNullKeySerializer(ValueSerializer<?> nks) {
        return withConfig(_factoryConfig.withNullKeySerializer(nks));
    }

    /*
    /**********************************************************************
    /* `SerializerFactory` impl
    /**********************************************************************
     */

// Implemented by sub-classes
//    public abstract ValueSerializer<Object> createSerializer(SerializationContext ctxt, ....)

    @Override
    @SuppressWarnings("unchecked")
    public ValueSerializer<Object> createKeySerializer(SerializationContext ctxt, JavaType keyType)
    {
        BeanDescription.Supplier beanDescRef = ctxt.lazyIntrospectBeanDescription(keyType);
        final SerializationConfig config = ctxt.getConfig();
        ValueSerializer<?> ser = null;
        // Minor optimization: to avoid constructing beanDesc, bail out if none registered
        if (_factoryConfig.hasKeySerializers()) {
            // Only thing we have here are module-provided key serializers:
            for (Serializers serializers : _factoryConfig.keySerializers()) {
                ser = serializers.findSerializer(config, keyType, beanDescRef, null);
                if (ser != null) {
                    break;
                }
            }
        }
        if (ser == null) {
            // [databind#2503]: Support `@Json[De]Serialize(keyUsing)` on key type too
            ser = _findKeySerializer(ctxt, beanDescRef.getClassInfo());
            if (ser == null) {
                // If no explicit serializer, see if type is JDK one for which there is
                // explicit deserializer: if so, can avoid further annotation lookups:
                ser = JDKKeySerializers.getStdKeySerializer(config, keyType.getRawClass(), false);
                if (ser == null) {
                    final BeanDescription beanDesc = beanDescRef.get();
                    // Check `@JsonKey` and `@JsonValue`, in this order
                    AnnotatedMember acc = beanDesc.findJsonKeyAccessor();
                    if (acc == null) {
                        acc = beanDesc.findJsonValueAccessor();
                    }
                    if (acc != null) {
                        ValueSerializer<?> delegate = createKeySerializer(ctxt, acc.getType());
                        if (config.canOverrideAccessModifiers()) {
                            ClassUtil.checkAndFixAccess(acc.getMember(),
                                    config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
                        }
                        // need to pass both type of key Object (on which accessor called), and actual
                        // value type that `JsonType`-annotated accessor returns (or contains, in case of field)
                        ser = JsonValueSerializer.construct(config, keyType, acc.getType(),
                                false, null, delegate, acc);
                    } else {
                        ser = JDKKeySerializers.getFallbackKeySerializer(config, keyType.getRawClass(),
                                beanDesc.getClassInfo());
                    }
                }
            }
        }

        // [databind#120]: Allow post-processing
        if (_factoryConfig.hasSerializerModifiers()) {
            for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                ser = mod.modifyKeySerializer(config, keyType, beanDescRef, ser);
            }
        }
        return (ValueSerializer<Object>) ser;
    }

    @Override
    public ValueSerializer<Object> getDefaultNullKeySerializer() {
        return _factoryConfig.getNullKeySerializer();
    }

    @Override
    public ValueSerializer<Object> getDefaultNullValueSerializer() {
        return _factoryConfig.getNullValueSerializer();
    }

    /*
    /**********************************************************************
    /* Additional API for other core classes
    /**********************************************************************
     */

    protected Iterable<Serializers> customSerializers() {
        return _factoryConfig.serializers();
    }

    /**
     * Method called to create a type information serializer for values of given
     * container property
     * if one is needed. If not needed (no polymorphic handling configured), should
     * return null.
     *
     * @param containerType Declared type of the container to use as the base type for type information serializer
     *
     * @return Type serializer to use for property value contents, if one is needed; null if not.
     */
    public TypeSerializer findPropertyContentTypeSerializer(SerializationContext ctxt,
            JavaType containerType, AnnotatedMember accessor)
    {
        return ctxt.getConfig().getTypeResolverProvider()
                .findPropertyContentTypeSerializer(ctxt, accessor, containerType);
    }

    /*
    /**********************************************************************
    /* Secondary serializer accessor methods
    /**********************************************************************
     */

    /**
     * Method called to see if one of primary per-class annotations
     * (or related, like implementing of {@link JacksonSerializable})
     * determines the serializer to use.
     *<p>
     * Currently handles things like:
     *<ul>
     * <li>If type implements {@link JacksonSerializable}, use that
     *  </li>
     * <li>If type has {@link com.fasterxml.jackson.annotation.JsonValue} annotation (or equivalent), build serializer
     *    based on that property
     *  </li>
     *</ul>
     */
    protected final ValueSerializer<?> findSerializerByAnnotations(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDescRef)
    {
        // First: serializable by Jackson-specific interface?
        if (type.isTypeOrSubTypeOf(JacksonSerializable.class)) {
            return JacksonSerializableSerializer.instance;
        }
        // Second: @JsonValue for any type
        AnnotatedMember valueAccessor = beanDescRef.get().findJsonValueAccessor();
        if (valueAccessor != null) {
            if (ctxt.canOverrideAccessModifiers()) {
                ClassUtil.checkAndFixAccess(valueAccessor.getMember(),
                        ctxt.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS));
            }
            ValueSerializer<Object> ser = findSerializerFromAnnotation(ctxt, valueAccessor);
            JavaType valueType = valueAccessor.getType();
            TypeSerializer vts = ctxt.findTypeSerializer(valueType);
            return JsonValueSerializer.construct(ctxt.getConfig(), type, valueType,
                    /* static typing */ false, vts, ser, valueAccessor);
        }
        // No well-known annotations...
        return null;
    }

    /**
     * Method for checking if we can determine serializer to use based on set of
     * known primary types, checking for set of known base types (exact matches
     * having been compared against with <code>findSerializerByLookup</code>).
     * This does not include "secondary" interfaces, but
     * mostly concrete or abstract base classes.
     */
    protected final ValueSerializer<?> findSerializerByPrimaryType(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
            boolean staticTyping)
    {
        // First: simple lookups for concrete types
        final Class<?> raw = type.getRawClass();
        ValueSerializer<?> ser;
        
        if ((ser = JDKCoreSerializers.find(raw)) != null) {
             return ser;
        }
        if ((ser = JDKStringLikeSerializer.find(raw)) != null) {
             return ser;
        }
        if ((ser = JDKMiscSerializers.find(raw)) != null) {
             return ser;
        }

        if (type.isTypeOrSubTypeOf(Calendar.class)) {
            return JavaUtilCalendarSerializer.instance;
        }
        if (type.isTypeOrSubTypeOf(Date.class)) {
            // 06-Nov-2020, tatu: Strange precedence challenge; need to consider
            //   "java.sql.Date" unfortunately
            if (!type.hasRawClass(Date.class)) {
                if ((ser = OptionalHandlerFactory.instance.findSerializer(ctxt.getConfig(), type) ) != null) {
                    return ser;
                }
            }
            return JavaUtilDateSerializer.instance;
        }
        // 19-Sep-2017, tatu: Jackson 3.x adds Java 8 types.
        // NOTE: while seemingly more of an add-on type, we must handle here because
        //   otherwise Bean-handling would be used instead...
        if (type.isTypeOrSubTypeOf(Stream.class)) {
            return new Jdk8StreamSerializer(type,
                    ctxt.getTypeFactory().findFirstTypeParameter(type, Stream.class));
        }

        if (type.isTypeOrSubTypeOf(Number.class)) {
            final Class<?> rawType = type.getRawClass();
            JsonFormat.Value format = _calculateEffectiveFormat(ctxt,
                    beanDescRef, rawType, formatOverrides);
            // 21-May-2014, tatu: Couple of alternatives actually
            switch (format.getShape()) {
            case STRING:
                return ToStringSerializer.instance;
            case OBJECT: // need to bail out to let it be serialized as POJO
                return null;
            default:
            }
            @SuppressWarnings("unchecked")
            Class<? extends Number> numberType = (Class<? extends Number>) rawType;
            return new NumberSerializer(numberType);
        }
        if (type.isTypeOrSubTypeOf(Map.Entry.class)) {
            // 18-Oct-2015, tatu: With 2.7, need to dig type info:
            JavaType mapEntryType = type.findSuperType(Map.Entry.class);
            // 28-Apr-2015, tatu: TypeFactory does it all for us already so
            JavaType kt = mapEntryType.containedTypeOrUnknown(0);
            JavaType vt = mapEntryType.containedTypeOrUnknown(1);
            return buildMapEntrySerializer(ctxt, type, beanDescRef,
                    _calculateEffectiveFormat(ctxt, beanDescRef, Map.Entry.class, formatOverrides),
                    staticTyping, kt, vt);
        }
        if (ByteBuffer.class.isAssignableFrom(raw)) {
            return new ByteBufferSerializer();
        }
        if (InetAddress.class.isAssignableFrom(raw)) {
            return new InetAddressSerializer();
        }
        if (InetSocketAddress.class.isAssignableFrom(raw)) {
            return new InetSocketAddressSerializer();
        }
        if (TimeZone.class.isAssignableFrom(raw)) {
            return new TimeZoneSerializer();
        }
        if (java.nio.charset.Charset.class.isAssignableFrom(raw)) {
            return ToStringSerializer.instance;
        }
        // 19-Sep-2017, tatu: Java 8 streams, except for main `Stream` (which is "add-on" interface?)
        if (LongStream.class.isAssignableFrom(raw)) {
            return LongStreamSerializer.INSTANCE;
        }
        if (IntStream.class.isAssignableFrom(raw)) {
            return IntStreamSerializer.INSTANCE;
        }
        if (DoubleStream.class.isAssignableFrom(raw)) {
            return DoubleStreamSerializer.INSTANCE;
        }
        // NOTE: not concrete, cannot just add directly via StdJdkSerializers. Also, requires
        // bit of trickery wrt class name for polymorphic...
        if (Path.class.isAssignableFrom(raw)) {
            return JDKStringLikeSerializer.find(Path.class);
        }
        // 23-Apr-2021, tatu: [databind#3130]: Suppress ClassLoader...
        if (ClassLoader.class.isAssignableFrom(raw)) {
            return new ToEmptyObjectSerializer(type);
        }
        // Then check for optional/external serializers
        return OptionalHandlerFactory.instance.findSerializer(ctxt.getConfig(), type);
    }

    /**
     * Reflection-based serialized find method, which checks if
     * given class implements one of recognized "add-on" interfaces.
     * Add-on here means a role that is usually or can be a secondary
     * trait: for example,
     * bean classes may implement {@link Iterable}, but their main
     * function is usually something else. The reason for
     */
    protected final ValueSerializer<?> findSerializerByAddonType(SerializationContext ctxt,
            JavaType javaType, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
            boolean staticTyping)
    {
        final TypeFactory tf = ctxt.getTypeFactory();
        if (javaType.isTypeOrSubTypeOf(Iterator.class)) {
            return buildIteratorSerializer(ctxt, javaType, beanDescRef, formatOverrides,
                    staticTyping,
                    tf.findFirstTypeParameter(javaType, Iterator.class));
        }
        if (javaType.isTypeOrSubTypeOf(Iterable.class)) {
            return buildIterableSerializer(ctxt, javaType, beanDescRef, formatOverrides,
                    staticTyping,
                    tf.findFirstTypeParameter(javaType, Iterable.class));
        }
        if (javaType.isTypeOrSubTypeOf(CharSequence.class)) {
            return ToStringSerializer.instance;
        }
        return null;
    }

    /**
     * Helper method called to check if a class or method
     * has an annotation
     * (@link tools.jackson.databind.annotation.JsonSerialize#using)
     * that tells the class to use for serialization.
     * Returns null if no such annotation found.
     */
    @SuppressWarnings("unchecked")
    protected ValueSerializer<Object> findSerializerFromAnnotation(SerializationContext ctxt,
            Annotated a)
    {
        Object serDef = ctxt.getAnnotationIntrospector().findSerializer(ctxt.getConfig(), a);
        if (serDef == null) {
            return null;
        }
        // One more thing however: may need to also apply a converter:
        return (ValueSerializer<Object>) findConvertingSerializer(ctxt, a,
                ctxt.serializerInstance(a, serDef));
    }

    /**
     * Helper method that will check whether given annotated entity (usually class,
     * but may also be a property accessor) indicates that a {@link Converter} is to
     * be used; and if so, to construct and return suitable serializer for it.
     * If not, will simply return given serializer as is.
     */
    protected ValueSerializer<?> findConvertingSerializer(SerializationContext ctxt,
            Annotated a, ValueSerializer<?> ser)
    {
        Converter<Object,Object> conv = findConverter(ctxt, a);
        if (conv == null) {
            return ser;
        }
        JavaType delegateType = conv.getOutputType(ctxt.getTypeFactory());
        return new StdDelegatingSerializer(conv, delegateType, ser, null);
    }

    protected Converter<Object,Object> findConverter(SerializationContext ctxt,
            Annotated a)
    {
        Object convDef = ctxt.getAnnotationIntrospector().findSerializationConverter(ctxt.getConfig(), a);
        if (convDef == null) {
            return null;
        }
        return ctxt.converterInstance(a, convDef);
    }

    /*
    /**********************************************************************
    /* Factory methods, container types:
    /**********************************************************************
     */

    protected ValueSerializer<?> buildContainerSerializer(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
            boolean staticTyping)
    {
        // [databind#23], 15-Mar-2013, tatu: must force static handling of root value type,
        //   with just one important exception: if value type is "untyped", let's
        //   leave it as is; no clean way to make it work.
        if (!staticTyping && type.useStaticType()) {
            if (!type.isContainerType() || !type.getContentType().isJavaLangObject()) {
                staticTyping = true;
            }
        }
        // Let's see what we can learn about element/content/value type, type serializer for it:
        JavaType elementType = type.getContentType();
        TypeSerializer elementTypeSerializer = ctxt.findTypeSerializer(elementType);
        // if elements have type serializer, cannot force static typing:
        if (elementTypeSerializer != null) {
            staticTyping = false;
        }
        ValueSerializer<Object> elementValueSerializer = _findContentSerializer(ctxt,
                beanDescRef.getClassInfo());
        final SerializationConfig config = ctxt.getConfig();
        if (type.isMapLikeType()) { // implements java.util.Map
            MapLikeType mlt = (MapLikeType) type;
            /* 29-Sep-2012, tatu: This is actually too early to (try to) find
             *  key serializer from property annotations, and can lead to caching
             *  issues (see [databind#75]). Instead, must be done from 'createContextual()' call.
             *  But we do need to check class annotations.
             */
            ValueSerializer<Object> keySerializer = _findKeySerializer(ctxt, beanDescRef.getClassInfo());
            if (mlt instanceof MapType mapType) {
                return buildMapSerializer(ctxt, mapType,
                        beanDescRef, formatOverrides, staticTyping,
                        keySerializer, elementTypeSerializer, elementValueSerializer);
            }
            // With Map-like, just 2 options: (1) Custom, (2) Annotations
            ValueSerializer<?> ser = null;
            MapLikeType mlType = (MapLikeType) type;
            for (Serializers serializers : customSerializers()) { // (1) Custom
                ser = serializers.findMapLikeSerializer(config, mlType,
                        beanDescRef, formatOverrides,
                        keySerializer, elementTypeSerializer, elementValueSerializer);
                if (ser != null) {
                    break;
                }
            }
            if (ser == null) { // (2) Annotations-based ones:
                ser = findSerializerByAnnotations(ctxt, type, beanDescRef);
            }
            if (ser != null) {
                if (_factoryConfig.hasSerializerModifiers()) {
                    for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                        ser = mod.modifyMapLikeSerializer(config, mlType, beanDescRef, ser);
                    }
                }
            }
            return ser;
        }
        if (type.isCollectionLikeType()) {
            CollectionLikeType clt = (CollectionLikeType) type;
            if (clt instanceof CollectionType collectionType) {
                return buildCollectionSerializer(ctxt, collectionType,
                        beanDescRef, formatOverrides, staticTyping,
                        elementTypeSerializer, elementValueSerializer);
            }
            // With Collection-like, just 2 options: (1) Custom, (2) Annotations
            ValueSerializer<?> ser = null;
            CollectionLikeType clType = (CollectionLikeType) type;
            for (Serializers serializers : customSerializers()) { // (1) Custom
                ser = serializers.findCollectionLikeSerializer(config, clType,
                        beanDescRef, formatOverrides,
                        elementTypeSerializer, elementValueSerializer);
                if (ser != null) {
                    break;
                }
            }
            if (ser == null) { // (2) Annotations-based ones:
                ser = findSerializerByAnnotations(ctxt, type, beanDescRef);
            }
            if (ser != null) {
                if (_factoryConfig.hasSerializerModifiers()) {
                    for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                        ser = mod.modifyCollectionLikeSerializer(config, clType, beanDescRef, ser);
                    }
                }
            }
            return ser;
        }
        if (type.isArrayType()) {
            return buildArraySerializer(ctxt, (ArrayType) type,
                    beanDescRef, formatOverrides, staticTyping,
                    elementTypeSerializer, elementValueSerializer);
        }
        return null;
    }

    /**
     * Helper method that handles configuration details when constructing serializers for
     * {@link java.util.List} types that support efficient by-index access
     */
    protected ValueSerializer<?> buildCollectionSerializer(SerializationContext ctxt,
            CollectionType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
            boolean staticTyping,
            TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer)
    {
        SerializationConfig config = ctxt.getConfig();
        ValueSerializer<?> ser = null;
        // Order of lookups:
        // 1. Custom serializers
        // 2. Annotations (@JsonValue, @JsonDeserialize)
        // 3. Defaults
        for (Serializers serializers : customSerializers()) { // (1) Custom
            ser = serializers.findCollectionSerializer(config, type, beanDescRef, formatOverrides,
                    elementTypeSerializer, elementValueSerializer);
            if (ser != null) {
                break;
            }
        }

        if (ser == null) {
            ser = findSerializerByAnnotations(ctxt, type, beanDescRef); // (2) Annotations
            if (ser == null) {
                JsonFormat.Value format = _calculateEffectiveFormat(ctxt,
                        beanDescRef, Collection.class, formatOverrides);
                // We may also want to use serialize Collections "as beans", if (and only if)
                // shape specified as "POJO"
                if (format.getShape() == JsonFormat.Shape.POJO) {
                    return null;
                }
                if (type.isTypeOrSubTypeOf(EnumSet.class)) {
                    // this may or may not be available (Class doesn't; type of field/method does)
                    JavaType enumType = type.getContentType();
                    // and even if nominally there is something, only use if it really is enum
                    if (!enumType.isEnumImplType()) { // usually since it's `Enum.class`
                        enumType = null;
                    }
                    ser = buildEnumSetSerializer(enumType);
                } else {
                    Class<?> elementRaw = type.getContentType().getRawClass();
                    if (isIndexedList(type.getRawClass())) {
                        if (elementRaw == String.class) {
                            // Only optimize if std implementation, not custom
                            if (ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
                                ser = IndexedStringListSerializer.instance;
                            }
                        } else {
                            ser = buildIndexedListSerializer(type.getContentType(), staticTyping,
                                elementTypeSerializer, elementValueSerializer);
                        }
                    } else if (elementRaw == String.class) {
                        // Only optimize if std implementation, not custom
                        if (ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
                            ser = StringCollectionSerializer.instance;
                        }
                    }
                    if (ser == null) {
                        ser = buildCollectionSerializer(type.getContentType(), staticTyping,
                                elementTypeSerializer, elementValueSerializer);
                    }
                }
            }
        }
        // [databind#120]: Allow post-processing
        if (_factoryConfig.hasSerializerModifiers()) {
            for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                ser = mod.modifyCollectionSerializer(config, type, beanDescRef, ser);
            }
        }
        return ser;
    }

    /*
    /**********************************************************************
    /* Factory methods, for Collections
    /**********************************************************************
     */

    protected boolean isIndexedList(Class<?> cls)
    {
        return RandomAccess.class.isAssignableFrom(cls);
    }

    public  StdContainerSerializer<?> buildIndexedListSerializer(JavaType elemType,
            boolean staticTyping, TypeSerializer vts, ValueSerializer<Object> valueSerializer) {
        return new IndexedListSerializer(elemType, staticTyping, vts, valueSerializer);
    }

    public StdContainerSerializer<?> buildCollectionSerializer(JavaType elemType,
            boolean staticTyping, TypeSerializer vts, ValueSerializer<Object> valueSerializer) {
        return new CollectionSerializer(elemType, staticTyping, vts, valueSerializer);
    }

    public ValueSerializer<?> buildEnumSetSerializer(JavaType enumType) {
        return new EnumSetSerializer(enumType);
    }

    /*
    /**********************************************************************
    /* Factory methods, for Maps
    /**********************************************************************
     */

    /**
     * Helper method that handles configuration details when constructing serializers for
     * {@link java.util.Map} types.
     */
    protected ValueSerializer<?> buildMapSerializer(SerializationContext ctxt,
            MapType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
            boolean staticTyping, ValueSerializer<Object> keySerializer,
            TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer)
    {
        ValueSerializer<?> ser = null;

        // Order of lookups:
        // 1. Custom serializers
        // 2. Annotations (@JsonValue, @JsonDeserialize)
        // 3. Defaults

        final SerializationConfig config = ctxt.getConfig();
        for (Serializers serializers : customSerializers()) { // (1) Custom
            ser = serializers.findMapSerializer(config, type, beanDescRef, formatOverrides,
                    keySerializer, elementTypeSerializer, elementValueSerializer);
            if (ser != null) { break; }
        }
        if (ser == null) {
            ser = findSerializerByAnnotations(ctxt, type, beanDescRef); // (2) Annotations
            if (ser == null) {
                JsonFormat.Value format = _calculateEffectiveFormat(ctxt,
                        beanDescRef, Map.class, formatOverrides);

                // [databind#467]: This is where we could allow serialization "as POJO": But! It's
                // nasty to undo, and does not apply on per-property basis. So, hardly optimal
                if (format.getShape() == JsonFormat.Shape.POJO) {
                    return null;
                }

                Object filterId = findFilterId(config, beanDescRef);
                // 01-May-2016, tatu: Which base type to use here gets tricky, since
                //   most often it ought to be `Map` or `EnumMap`, but due to abstract
                //   mapping it will more likely be concrete type like `HashMap`.
                //   So, for time being, just pass `Map.class`
                final AnnotatedClass classInfo = beanDescRef.getClassInfo();
                JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(Map.class,
                        classInfo);
                Set<String> ignored = (ignorals == null) ? null
                        : ignorals.findIgnoredForSerialization();
                JsonIncludeProperties.Value inclusions = config.getDefaultPropertyInclusions(Map.class,
                        classInfo);
                Set<String> included = (inclusions == null) ? null
                        : inclusions.getIncluded();
                MapSerializer mapSer = MapSerializer.construct(type,
                        staticTyping, elementTypeSerializer,
                        keySerializer, elementValueSerializer, filterId,
                        ignored, included);
                ser = _checkMapContentInclusion(ctxt, beanDescRef, mapSer);
            }
        }
        // [databind#120]: Allow post-processing
        if (_factoryConfig.hasSerializerModifiers()) {
            for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                ser = mod.modifyMapSerializer(config, type, beanDescRef, ser);
            }
        }
        return ser;
    }

    /**
     * Helper method that does figures out content inclusion value to use, if any,
     * and construct re-configured {@link MapSerializer} appropriately.
     */
    protected MapSerializer _checkMapContentInclusion(SerializationContext ctxt,
            BeanDescription.Supplier beanDescRef, MapSerializer mapSer)
    {
        final JavaType contentType = mapSer.getContentType();
        JsonInclude.Value inclV = _findInclusionWithContent(ctxt, beanDescRef,
                contentType, Map.class);

        // Need to support global legacy setting, for now:
        JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
        if (incl == JsonInclude.Include.USE_DEFAULTS
                || incl == JsonInclude.Include.ALWAYS) {
            return mapSer;
        }

        // NOTE: mostly copied from `PropertyBuilder`; would be nice to refactor
        // but code is not identical nor are these types related
        Object valueToSuppress;
        boolean suppressNulls = true; // almost always, but possibly not with CUSTOM

        switch (incl) {
        case NON_DEFAULT:
            valueToSuppress = BeanUtil.getDefaultValue(contentType);
            if (valueToSuppress != null) {
                if (valueToSuppress.getClass().isArray()) {
                    valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
                }
            }
            break;
        case NON_ABSENT: // new with 2.6, to support Guava/JDK8 Optionals
            // and for referential types, also "empty", which in their case means "absent"
            valueToSuppress = contentType.isReferenceType()
                    ? MapSerializer.MARKER_FOR_EMPTY : null;
            break;
        case NON_EMPTY:
            valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
            break;
        case CUSTOM: // new with 2.9
            valueToSuppress = ctxt.includeFilterInstance(null, inclV.getContentFilter());
            if (valueToSuppress != null) { // is this legal?
                suppressNulls = ctxt.includeFilterSuppressNulls(valueToSuppress);
            }
            break;
        case NON_NULL:
        default: // should not matter but...
            valueToSuppress = null;
            break;
        }
        return mapSer.withContentInclusion(valueToSuppress, suppressNulls);
    }

    protected ValueSerializer<?> buildMapEntrySerializer(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDescRef,  JsonFormat.Value effectiveFormat,
            boolean staticTyping,
            JavaType keyType, JavaType valueType)
    {
        // [databind#865]: Allow serialization "as POJO" -- note: to undo, declare
        //   serialization as `Shape.NATURAL` instead; that's JSON Object too.
        if (effectiveFormat.getShape() == JsonFormat.Shape.POJO) {
            return null;
        }
        MapEntrySerializer ser = new MapEntrySerializer(valueType, keyType,
                valueType, staticTyping, ctxt.findTypeSerializer(valueType), null);

        final JavaType contentType = ser.getContentType();
        JsonInclude.Value inclV = _findInclusionWithContent(ctxt, beanDescRef,
                contentType, Map.Entry.class);

        // Need to support global legacy setting, for now:
        JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
        if (incl == JsonInclude.Include.USE_DEFAULTS
                || incl == JsonInclude.Include.ALWAYS) {
            return ser;
        }

        // NOTE: mostly copied from `PropertyBuilder`; would be nice to refactor
        // but code is not identical nor are these types related
        Object valueToSuppress;
        boolean suppressNulls = true; // almost always, but possibly not with CUSTOM

        switch (incl) {
        case NON_DEFAULT:
            valueToSuppress = BeanUtil.getDefaultValue(contentType);
            if (valueToSuppress != null) {
                if (valueToSuppress.getClass().isArray()) {
                    valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
                }
            }
            break;
        case NON_ABSENT:
            valueToSuppress = contentType.isReferenceType()
                    ? MapSerializer.MARKER_FOR_EMPTY : null;
            break;
        case NON_EMPTY:
            valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
            break;
        case CUSTOM:
            valueToSuppress = ctxt.includeFilterInstance(null, inclV.getContentFilter());
            if (valueToSuppress != null) { // is this legal?
                suppressNulls = ctxt.includeFilterSuppressNulls(valueToSuppress);
            }
            break;
        case NON_NULL:
        default: // should not matter but...
            valueToSuppress = null;
            break;
        }
        return ser.withContentInclusion(valueToSuppress, suppressNulls);
    }

    /**
     * Helper method used for finding inclusion definitions for structured
     * container types like <code>Map</code>s and referential types
     * (like <code>AtomicReference</code>).
     *
     * @param contentType Declared full content type of container
     * @param configType Raw base type under which `configOverride`, if any, needs to be defined
     */
    protected JsonInclude.Value _findInclusionWithContent(SerializationContext ctxt,
            BeanDescription.Supplier beanDescRef,
            JavaType contentType, Class<?> configType)
    {
        final SerializationConfig config = ctxt.getConfig();

        // Defaulting gets complicated because we might have two distinct
        //   axis to consider: Container type itself , and then value (content) type.
        //  Start with Container-defaults, then use more-specific value override, if any.

        // Start by getting global setting, overridden by Map-type-override
        JsonInclude.Value inclV = beanDescRef.get().findPropertyInclusion(config.getDefaultPropertyInclusion());
        inclV = config.getDefaultPropertyInclusion(configType, inclV);

        // and then merge content-type overrides, if any. But note that there's
        // content-to-value inclusion shift we have to do
        JsonInclude.Value valueIncl = config.getDefaultPropertyInclusion(contentType.getRawClass(), null);

        if (valueIncl != null) {
            switch (valueIncl.getValueInclusion()) {
            case USE_DEFAULTS:
                break;
            case CUSTOM:
                inclV = inclV.withContentFilter(valueIncl.getContentFilter());
                break;
            default:
                inclV = inclV.withContentInclusion(valueIncl.getValueInclusion());
            }
        }
        return inclV;
    }

    /*
    /**********************************************************************
    /* Factory methods, for Arrays
    /**********************************************************************
     */

    /**
     * Helper method that handles configuration details when constructing serializers for
     * <code>Object[]</code> (and subtypes, except for String).
     */
    protected ValueSerializer<?> buildArraySerializer(SerializationContext ctxt,
            ArrayType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value formatOverrides,
            boolean staticTyping,
            TypeSerializer elementTypeSerializer, ValueSerializer<Object> elementValueSerializer)
    {
        // 25-Jun-2015, tatu: Note that unlike with Collection(Like) and Map(Like) types, array
        //   types cannot be annotated (in theory I guess we could have mix-ins but... ?)
        //   so we need not do primary annotation lookup here.
        //   So all we need is (1) Custom, (2) Default array serializers
        SerializationConfig config = ctxt.getConfig();
        ValueSerializer<?> ser = null;

        for (Serializers serializers : customSerializers()) { // (1) Custom
             if ((ser = serializers.findArraySerializer(config, type, beanDescRef, formatOverrides,
                     elementTypeSerializer, elementValueSerializer)) != null) {
                 break;
             }
        }

        if (ser == null) {
             Class<?> raw = type.getRawClass();
             // Important: do NOT use standard serializers if non-standard element value serializer specified
             if (elementValueSerializer == null || ClassUtil.isJacksonStdImpl(elementValueSerializer)) {
                 if (String[].class == raw) {
                     ser = StringArraySerializer.instance;
                 } else {
                     // other standard types?
                     ser = JDKArraySerializers.findStandardImpl(ctxt, raw, formatOverrides);
                 }
             }
             if (ser == null) {
                 ser = new ObjectArraySerializer(type.getContentType(), staticTyping, elementTypeSerializer,
                         elementValueSerializer);
             }
         }
         // [databind#120]: Allow post-processing
         if (_factoryConfig.hasSerializerModifiers()) {
             for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                 ser = mod.modifyArraySerializer(config, type, beanDescRef, ser);
             }
         }
         return ser;
    }

    /*
    /**********************************************************************
    /* Factory methods for Reference types
    /**********************************************************************
     */

    public ValueSerializer<?> findReferenceSerializer(SerializationContext ctxt,
            ReferenceType refType, BeanDescription.Supplier beanDesc, JsonFormat.Value format,
            boolean staticTyping)
    {
        JavaType contentType = refType.getContentType();
        TypeSerializer contentTypeSerializer = (TypeSerializer) contentType.getTypeHandler();
        final SerializationConfig config = ctxt.getConfig();
        if (contentTypeSerializer == null) {
            contentTypeSerializer = ctxt.findTypeSerializer(contentType);
        }
        @SuppressWarnings("unchecked")
        ValueSerializer<Object> contentSerializer = (ValueSerializer<Object>) contentType.getValueHandler();
        for (Serializers serializers : customSerializers()) {
            ValueSerializer<?> ser = serializers.findReferenceSerializer(config, refType, beanDesc, format,
                    contentTypeSerializer, contentSerializer);
            if (ser != null) {
                return ser;
            }
        }
        if (refType.isTypeOrSubTypeOf(AtomicReference.class)) {
            return _buildReferenceSerializer(ctxt, AtomicReference.class,
                    refType, beanDesc, staticTyping,
                    contentTypeSerializer, contentSerializer);
        }
        if (refType.isTypeOrSubTypeOf(Optional.class)) {
            return _buildReferenceSerializer(ctxt, Optional.class,
                    refType, beanDesc, staticTyping,
                    contentTypeSerializer, contentSerializer);
        }
        if (refType.isTypeOrSubTypeOf(OptionalInt.class)) {
            return new OptionalIntSerializer();
        }
        if (refType.isTypeOrSubTypeOf(OptionalLong.class)) {
            return new OptionalLongSerializer();
        }
        if (refType.isTypeOrSubTypeOf(OptionalDouble.class)) {
            return new OptionalDoubleSerializer();
        }
        return null;
    }

    protected ValueSerializer<?> _buildReferenceSerializer(SerializationContext ctxt, Class<?> baseType,
            ReferenceType refType, BeanDescription.Supplier beanDescRef, boolean staticTyping,
            TypeSerializer contentTypeSerializer, ValueSerializer<Object> contentSerializer)
    {
        final JavaType contentType = refType.getReferencedType();
        JsonInclude.Value inclV = _findInclusionWithContent(ctxt, beanDescRef,
                contentType, baseType);

        // Need to support global legacy setting, for now:
        JsonInclude.Include incl = (inclV == null) ? JsonInclude.Include.USE_DEFAULTS : inclV.getContentInclusion();
        Object valueToSuppress;
        boolean suppressNulls;

        if (incl == JsonInclude.Include.USE_DEFAULTS || incl == JsonInclude.Include.ALWAYS) {
            valueToSuppress = null;
            suppressNulls = false;
        } else {
            suppressNulls = true;
            switch (incl) {
            case NON_DEFAULT:
                valueToSuppress = BeanUtil.getDefaultValue(contentType);
                if (valueToSuppress != null) {
                    if (valueToSuppress.getClass().isArray()) {
                        valueToSuppress = ArrayBuilders.getArrayComparator(valueToSuppress);
                    }
                }
                break;
            case NON_ABSENT:
                valueToSuppress = contentType.isReferenceType()
                        ? MapSerializer.MARKER_FOR_EMPTY : null;
                break;
            case NON_EMPTY:
                valueToSuppress = MapSerializer.MARKER_FOR_EMPTY;
                break;
            case CUSTOM:
                valueToSuppress = ctxt.includeFilterInstance(null, inclV.getContentFilter());
                if (valueToSuppress == null) { // is this legal?
                    suppressNulls = true;
                } else {
                    suppressNulls = ctxt.includeFilterSuppressNulls(valueToSuppress);
                }
                break;
            case NON_NULL:
            default: // should not matter but...
                valueToSuppress = null;
                break;
            }
        }
        ReferenceTypeSerializer<?> ser;
        if (baseType == Optional.class) {
            ser = new Jdk8OptionalSerializer(refType, staticTyping,
                    contentTypeSerializer, contentSerializer);
        } else {
            ser = new AtomicReferenceSerializer(refType, staticTyping,
                    contentTypeSerializer, contentSerializer);
        }
        return ser.withContentInclusion(valueToSuppress, suppressNulls);
    }

    /*
    /**********************************************************************
    /* Factory methods, for non-container types
    /**********************************************************************
     */

    protected ValueSerializer<?> buildIteratorSerializer(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDesc, JsonFormat.Value formatOverrides,
            boolean staticTyping,
            JavaType valueType)
    {
        return new IteratorSerializer(valueType, staticTyping,
                ctxt.findTypeSerializer(valueType));
    }

    protected ValueSerializer<?> buildIterableSerializer(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDescRef,
            JsonFormat.Value effectiveFormat, boolean staticTyping,
            JavaType valueType)
    {
        return new IterableSerializer(valueType, staticTyping,
                ctxt.findTypeSerializer(valueType));
    }

    protected ValueSerializer<?> buildEnumSerializer(SerializationContext ctxt,
            JavaType type, BeanDescription.Supplier beanDescRef, JsonFormat.Value effectiveFormat)
    {
        // As per [databind#24], may want to use alternate shape, serialize as JSON Object.
        // Challenge here is that EnumSerializer does not know how to produce
        // POJO style serialization, so we must handle that special case separately;
        // otherwise pass it to EnumSerializer.
        JsonFormat.Shape shape = effectiveFormat.getShape();
        if (shape == JsonFormat.Shape.POJO || shape == JsonFormat.Shape.OBJECT) {
            final BeanDescription beanDesc = beanDescRef.get();
            // one special case: suppress serialization of "getDeclaringClass()"...
            ((BasicBeanDescription) beanDesc).removeProperty("declaringClass");
            // [databind#2787]: remove self-referencing enum fields introduced by annotation flattening of mixins
            if (type.isEnumType()){
                _removeEnumSelfReferences(beanDesc);
            }
            // returning null will mean that eventually BeanSerializer gets constructed
            return null;
        }
        @SuppressWarnings("unchecked")
        Class<Enum<?>> enumClass = (Class<Enum<?>>) type.getRawClass();
        final SerializationConfig config = ctxt.getConfig();
        ValueSerializer<?> ser = EnumSerializer.construct(enumClass, config,
                beanDescRef.get(), effectiveFormat);
        if (_factoryConfig.hasSerializerModifiers()) {
            for (ValueSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                ser = mod.modifyEnumSerializer(config, type, beanDescRef, ser);
            }
        }
        return ser;
    }

    /**
     * Helper method used for serialization {@link Enum} as {@link JsonFormat.Shape#OBJECT}.
     * Removes any  self-referencing properties from its bean description before it is
     * transformed into a JSON Object as configured by {@link JsonFormat.Shape#OBJECT}.
     * <p>
     * Internally, this method iterates through {@link BeanDescription#findProperties()}
     * and removes self references.
     *
     * @param beanDesc the bean description to remove Enum properties from.
     *
     * @since 2.16
     */
    private void _removeEnumSelfReferences(BeanDescription beanDesc) {
        Class<?> aClass = ClassUtil.findEnumType(beanDesc.getBeanClass());
        Iterator<BeanPropertyDefinition> it = beanDesc.findProperties().iterator();
        while (it.hasNext()) {
            BeanPropertyDefinition property = it.next();
            JavaType propType = property.getPrimaryType();
            // is the property a self-reference?
            if (propType.isEnumType() && propType.isTypeOrSubTypeOf(aClass)
                    // [databind#4564] Since 2.16.3, Enum's should allow self as field, so let's remove only if static.
                    && property.getAccessor().isStatic())
            {
                it.remove();
            }
        }
    }

    /*
    /**********************************************************************
    /* Other helper methods
    /**********************************************************************
     */

    /**
     * Helper method that will combine all available pieces of format configuration
     * and calculate effective format settings to use.
     *
     * @since 3.0
     */
    protected JsonFormat.Value _calculateEffectiveFormat(SerializationContext ctxt,
            BeanDescription.Supplier beanDescRef,
            Class<?> baseType, JsonFormat.Value formatOverrides)
    {
        JsonFormat.Value fromType = beanDescRef.findExpectedFormat(baseType);
        if (formatOverrides == null) {
            return fromType;
        }
        return JsonFormat.Value.merge(fromType, formatOverrides);
    }

    /**
     * Helper method called to try to find whether there is an annotation in the
     * class that indicates key serializer to use.
     * If so, will try to instantiate key serializer and return it; otherwise returns null.
     */
    protected ValueSerializer<Object> _findKeySerializer(SerializationContext ctxt,
            Annotated a)
    {
        AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
        Object serDef = intr.findKeySerializer(ctxt.getConfig(), a);
        return ctxt.serializerInstance(a, serDef);
    }

    /**
     * Helper method called to try to find whether there is an annotation in the
     * class that indicates content ("value") serializer to use.
     * If so, will try to instantiate value serializer and return it; otherwise returns null.
     */
    protected ValueSerializer<Object> _findContentSerializer(SerializationContext ctxt,
            Annotated a)
    {
        AnnotationIntrospector intr = ctxt.getAnnotationIntrospector();
        Object serDef = intr.findContentSerializer(ctxt.getConfig(), a);
        return ctxt.serializerInstance(a, serDef); // ok to pass null
    }

    /**
     * Method called to find filter that is configured to be used with bean
     * serializer being built, if any.
     */
    protected Object findFilterId(SerializationConfig config, BeanDescription.Supplier beanDescRef) {
        return config.getAnnotationIntrospector().findFilterId(config,
                (Annotated)beanDescRef.getClassInfo());
    }

    /**
     * Helper method to check whether global settings and/or class
     * annotations for the bean class indicate that static typing
     * (declared types)  should be used for properties.
     * (instead of dynamic runtime types).
     */
    protected boolean usesStaticTyping(SerializationConfig config,
            BeanDescription.Supplier beanDescRef)
    {
        JsonSerialize.Typing t = config.getAnnotationIntrospector()
                .findSerializationTyping(config, beanDescRef.getClassInfo());
        if (t != null) {
            switch (t) {
            case DYNAMIC:
                return false;
            case STATIC:
                return true;
            case DEFAULT_TYPING:
                // fall through
            }
        }
        return config.isEnabled(MapperFeature.USE_STATIC_TYPING);
    }
}