package org.codehaus.jackson.map;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Date;

import org.codehaus.jackson.*;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.schema.JsonSchema;
import org.codehaus.jackson.type.JavaType;

/**
 * Abstract class that defines API used by {@link ObjectMapper} and
 * {@link JsonSerializer}s to obtain serializers capable of serializing
 * instances of specific types.
 *<p>
 * Note about usage: for {@link JsonSerializer} instances, only accessors
 * for locating other (sub-)serializers are to be used. {@link ObjectMapper},
 * on the other hand, is to initialize recursive serialization process by
 * calling {@link #serializeValue}.
 */
public abstract class SerializerProvider
{
    protected final static JavaType TYPE_OBJECT = TypeFactory.defaultInstance().uncheckedSimpleType(Object.class);
    
    /**
     * Serialization configuration to use for serialization processing.
     */
    protected final SerializationConfig _config;

    /**
     * View used for currently active serialization
     */
    protected final Class<?> _serializationView;

    protected SerializerProvider(SerializationConfig config)
    {
        _config = config;
        _serializationView = (config == null) ? null : _config.getSerializationView();
    }

    /*
    /**********************************************************
    /* Methods for configuring default settings
    /**********************************************************
     */

    /**
     * Method that can be used to specify serializer that will be
     * used to write JSON property names matching null keys for Java
     * Maps (which will throw an exception if try write such property
     * name)
     * 
     * @since 1.8
     */
    public abstract void setNullKeySerializer(JsonSerializer<Object> nks);

    /**
     * Method that can be used to specify serializer that will be
     * used to write JSON values matching Java null values
     * instead of default one (which simply writes JSON null)
     * 
     * @since 1.8
     */
    public abstract void setNullValueSerializer(JsonSerializer<Object> nvs);
    
    /**
     * Method that can be used to specify serializer to use for serializing
     * all non-null JSON property names, unless more specific key serializer
     * is found (i.e. if not custom key serializer has been registered for
     * Java type).
     *<p>
     * Note that key serializer registration are different from value serializer
     * registrations.
     * 
     * @since 1.8
     */
    public abstract void setDefaultKeySerializer(JsonSerializer<Object> ks);
    
    /*
    /**********************************************************
    /* Methods that ObjectMapper will call
    /**********************************************************
     */

    /**
     * The method to be called by {@link ObjectMapper} to
     * execute recursive serialization, using serializers that
     * this provider has access to.
     *
     * @param jsf Underlying factory object used for creating serializers
     *    as needed
     */
    public abstract void serializeValue(SerializationConfig cfg, JsonGenerator jgen,
            Object value, SerializerFactory jsf)
        throws IOException, JsonGenerationException;

    /**
     * The method to be called by {@link ObjectMapper} to
     * execute recursive serialization, using serializers that
     * this provider has access to; and using specified root type
     * for locating first-level serializer.
     * 
     * @param rootType Type to use for locating serializer to use, instead of actual
     *    runtime type. Must be actual type, or one of its super types
     *    
     * @since 1.5
     */
    public abstract void serializeValue(SerializationConfig cfg, JsonGenerator jgen,
            Object value, JavaType rootType, SerializerFactory jsf)
        throws IOException, JsonGenerationException;
    
    /**
     * Generate <a href="http://json-schema.org/">Json-schema</a> for
     * given type.
     *
     * @param type The type for which to generate schema
     */
    public abstract JsonSchema generateJsonSchema(Class<?> type, SerializationConfig config, SerializerFactory jsf)
        throws JsonMappingException;

    /**
     * Method that can be called to see if this serializer provider
     * can find a serializer for an instance of given class.
     *<p>
     * Note that no Exceptions are thrown, including unchecked ones:
     * implementations are to swallow exceptions if necessary.
     */
    public abstract boolean hasSerializerFor(SerializationConfig cfg,
            Class<?> cls, SerializerFactory jsf);

    /*
    /**********************************************************
    /* Access to configuration
    /**********************************************************
     */

    /**
     * Method for accessing configuration for the serialization processing.
     */
    public final SerializationConfig getConfig() { return _config; }

    /**
     * Convenience method for checking whether specified serialization
     * feature is enabled or not.
     * Shortcut for:
     *<pre>
     *  getConfig().isEnabled(feature);
     *</pre>
     */
    public final boolean isEnabled(SerializationConfig.Feature feature) {
        return _config.isEnabled(feature);
    }

    /**
     * Convenience method for accessing serialization view in use (if any); equivalent to:
     *<pre>
     *   getConfig().getSerializationView();
     *</pre>
     *
     * @since 1.4
     */
    public final Class<?> getSerializationView() { return _serializationView; }

    /**
     * Convenience method for accessing provider to find serialization filters used,
     * equivalent to calling:
     *<pre>
     *   getConfig().getFilterProvider();
     *</pre>
     *
     * @since 1.4
     */
    public final FilterProvider getFilterProvider() {
        return _config.getFilterProvider();
    }

    /**
     * @since 1.8
     */
    public JavaType constructType(Type type) {
         return _config.getTypeFactory().constructType(type);
    }

    /**
     * @since 1.9.1
     */
    public JavaType constructSpecializedType(JavaType baseType, Class<?> subclass) {
        return _config.constructSpecializedType(baseType, subclass);
    }
    
    /*
    /**********************************************************
    /* General serializer locating functionality
    /**********************************************************
     */

    /**
     * Method called to get hold of a serializer for a value of given type;
     * or if no such serializer can be found, a default handler (which
     * may do a best-effort generic serialization or just simply
     * throw an exception when invoked).
     *<p>
     * Note: this method is only called for non-null values; not for keys
     * or null values. For these, check out other accessor methods.
     *<p>
     * Note that starting with version 1.5, serializers should also be type-aware
     * if they handle polymorphic types. That means that it may be necessary
     * to also use a {@link TypeSerializer} based on declared (static) type
     * being serializer (whereas actual data may be serialized using dynamic
     * type)
     *
     * @throws JsonMappingException if there are fatal problems with
     *   accessing suitable serializer; including that of not
     *   finding any serializer
     */
    public abstract JsonSerializer<Object> findValueSerializer(Class<?> runtimeType,
            BeanProperty property)
        throws JsonMappingException;

    /**
     * Similar to {@link #findValueSerializer(Class)}, but takes full generics-aware
     * type instead of raw class.
     * 
     * @since 1.5
     */
    public abstract JsonSerializer<Object> findValueSerializer(JavaType serializationType,
            BeanProperty property)
        throws JsonMappingException;
    
    /**
     * Method called to locate regular serializer, matching type serializer,
     * and if both found, wrap them in a serializer that calls both in correct
     * sequence. This method is currently only used for root-level serializer
     * handling to allow for simpler caching. A call can always be replaced
     * by equivalent calls to access serializer and type serializer separately.
     * 
     * @param valueType Type for purpose of locating a serializer; usually dynamic
     *   runtime type, but can also be static declared type, depending on configuration
     * 
     * @param cache Whether resulting value serializer should be cached or not; this is just
     *    a hint 
     *    
     * @since 1.5
     */
    public abstract JsonSerializer<Object> findTypedValueSerializer(Class<?> valueType,
            boolean cache, BeanProperty property)
        throws JsonMappingException;

    /**
     * Method called to locate regular serializer, matching type serializer,
     * and if both found, wrap them in a serializer that calls both in correct
     * sequence. This method is currently only used for root-level serializer
     * handling to allow for simpler caching. A call can always be replaced
     * by equivalent calls to access serializer and type serializer separately.
     * 
     * @param valueType Declared type of value being serialized (which may not
     *    be actual runtime type); used for finding both value serializer and
     *    type serializer to use for adding polymorphic type (if any)
     * 
     * @param cache Whether resulting value serializer should be cached or not; this is just
     *    a hint 
     *    
     * @since 1.5
     */
    public abstract JsonSerializer<Object> findTypedValueSerializer(JavaType valueType,
            boolean cache, BeanProperty property)
        throws JsonMappingException;

    /**
     * Method called to get the serializer to use for serializing
     * non-null Map keys. Separation from regular
     * {@link #findValueSerializer} method is because actual write
     * method must be different (@link JsonGenerator#writeFieldName};
     * but also since behavior for some key types may differ.
     *<p>
     * Note that the serializer itself can be called with instances
     * of any Java object, but not nulls.
     * 
     * @since 1.8
     */
    public abstract JsonSerializer<Object> findKeySerializer(JavaType keyType,
            BeanProperty property)
        throws JsonMappingException;

    /*
    /**********************************************************
    /* Deprecated serializer locating functionality
    /**********************************************************
     */

    /**
     * Deprecated version of accessor method that was used before version 1.7.
     * Implemented as final to ensure that existing code does not accidentally
     * try to redefine it (given that it is not called by core mapper code)
     *   
     * @deprecated As of version 1.7, use version that exposes property object
     *    instead of just its type (needed for contextual serializers)
     */
    @Deprecated
    public final JsonSerializer<Object> findValueSerializer(Class<?> runtimeType)
        throws JsonMappingException
    {
        return findValueSerializer(runtimeType, null);
    }

    /**
     * Deprecated version of accessor method that was used before version 1.7.
     * Implemented as final to ensure that existing code does not accidentally
     * try to redefine it (given that it is not called by core mapper code)
     *   
     * @deprecated As of version 1.7, use version that exposes property object
     *    instead of just its type (needed for contextual serializers)
     */
    @Deprecated
    public final JsonSerializer<Object> findValueSerializer(JavaType serializationType)
        throws JsonMappingException
    {
        return findValueSerializer(serializationType, null);
    }

    /**
     * Deprecated version of accessor method that was used before version 1.7.
     * Implemented as final to ensure that existing code does not accidentally
     * try to redefine it (given that it is not called by core mapper code)
     *   
     * @deprecated As of version 1.7, use version that exposes property object
     *    instead of just its type (needed for contextual serializers)
     */
    @Deprecated
    public final JsonSerializer<Object> findTypedValueSerializer(Class<?> valueType,
            boolean cache)
        throws JsonMappingException
    {
        return findTypedValueSerializer(valueType, cache, null);
    }

    /**
     * Deprecated version of accessor method that was used before version 1.7.
     * Implemented as final to ensure that existing code does not accidentally
     * try to redefine it (given that it is not called by core mapper code)
     *   
     * @deprecated As of version 1.7, use version that exposes property object
     *    instead of just its type (needed for contextual serializers)
     */
    @Deprecated
    public final JsonSerializer<Object> findTypedValueSerializer(JavaType valueType,
            boolean cache)
        throws JsonMappingException
    {
        return findTypedValueSerializer(valueType, cache, null);
    }

    /**
     * Deprecated version of accessor method that was used before version 1.7.
     * Implemented as final to ensure that existing code does not accidentally
     * try to redefine it (given that it is not called by core mapper code)
     *   
     * @deprecated As of version 1.7, use version that exposes property object
     *    instead of just its type (needed for contextual serializers)
     */
    @Deprecated
    public final JsonSerializer<Object> getKeySerializer()
        throws JsonMappingException
    {
        return findKeySerializer(TYPE_OBJECT, null);
    }

    /**
     * Deprecated version of accessor method that was used before version 1.8;
     * renamed as {@link #findKeySerializer}, since process is now
     * more complicated than simple lookup.
     *
     * @deprecated As of version 1.8
     */
    @Deprecated
    public final JsonSerializer<Object> getKeySerializer(JavaType valueType, BeanProperty property)
        throws JsonMappingException
    {
        return findKeySerializer(valueType, property);
    }
    
    /*
    /********************************************************
    /* Accessors for specialized serializers
    /********************************************************
     */

    /**
     * Method called to get the serializer to use for serializing
     * Map keys that are nulls: this is needed since JSON does not allow
     * any non-String value as key, including null.
     *<p>
     * Typically, returned serializer
     * will either throw an exception, or use an empty String; but
     * other behaviors are possible.
     */
    public abstract JsonSerializer<Object> getNullKeySerializer();

    /**
     * Method called to get the serializer to use for serializing
     * values (root level, Array members or List field values)
     * that are nulls. Specific accessor is needed because nulls
     * in Java do not contain type information.
     *<p>
     * Typically returned serializer just writes out Json literal
     * null value.
     */
    public abstract JsonSerializer<Object> getNullValueSerializer();

    /**
     * Method called to get the serializer to use if provider
     * can not determine an actual type-specific serializer
     * to use; typically when none of {@link SerializerFactory}
     * instances are able to construct a serializer.
     *<p>
     * Typically, returned serializer will throw an exception,
     * although alternatively {@link org.codehaus.jackson.map.ser.ToStringSerializer} could
     * be returned as well.
     *
     * @param unknownType Type for which no serializer is found
     */
    public abstract JsonSerializer<Object> getUnknownTypeSerializer(Class<?> unknownType);

    /*
    /********************************************************
    /* Convenience methods
    /********************************************************
     */

    /**
     * Convenience method that will serialize given value (which can be
     * null) using standard serializer locating functionality. It can
     * be called for all values including field and Map values, but usually
     * field values are best handled calling
     * {@link #defaultSerializeField} instead.
     */
    public final void defaultSerializeValue(Object value, JsonGenerator jgen)
        throws IOException, JsonProcessingException
    {
        if (value == null) {
            getNullValueSerializer().serialize(null, jgen, this);
        } else {
            Class<?> cls = value.getClass();
            findTypedValueSerializer(cls, true, null).serialize(value, jgen, this);
        }
    }
    
    /**
     * Convenience method that will serialize given field with specified
     * value. Value may be null. Serializer is done using the usual
     * null) using standard serializer locating functionality.
     */
    public final void defaultSerializeField(String fieldName, Object value, JsonGenerator jgen)
        throws IOException, JsonProcessingException
    {
        jgen.writeFieldName(fieldName);
        if (value == null) {
            /* Note: can't easily check for suppression at this point
             * any more; caller must check it.
             */
            getNullValueSerializer().serialize(null, jgen, this);
        } else {
            Class<?> cls = value.getClass();
            findTypedValueSerializer(cls, true, null).serialize(value, jgen, this);
        }
    }

    /**
     * Method that will handle serialization of Date(-like) values, using
     * {@link SerializationConfig} settings to determine expected serialization
     * behavior.
     * Note: date here means "full" date, that is, date AND time, as per
     * Java convention (and not date-only values like in SQL)
     */
    public abstract void defaultSerializeDateValue(long timestamp, JsonGenerator jgen)
        throws IOException, JsonProcessingException;

    /**
     * Method that will handle serialization of Date(-like) values, using
     * {@link SerializationConfig} settings to determine expected serialization
     * behavior.
     * Note: date here means "full" date, that is, date AND time, as per
     * Java convention (and not date-only values like in SQL)
     */
    public abstract void defaultSerializeDateValue(Date date, JsonGenerator jgen)
        throws IOException, JsonProcessingException;


    /**
     * Method that will handle serialization of Dates used as {@link java.util.Map} keys,
     * based on {@link SerializationConfig.Feature#WRITE_DATE_KEYS_AS_TIMESTAMPS}
     * value (and if using textual representation, configured date format)
     * 
     * @since 1.9
     */
    public abstract void defaultSerializeDateKey(long timestamp, JsonGenerator jgen)
        throws IOException, JsonProcessingException;

    /**
     * Method that will handle serialization of Dates used as {@link java.util.Map} keys,
     * based on {@link SerializationConfig.Feature#WRITE_DATE_KEYS_AS_TIMESTAMPS}
     * value (and if using textual representation, configured date format)
     * 
     * @since 1.9
     */
    public abstract void defaultSerializeDateKey(Date date, JsonGenerator jgen)
        throws IOException, JsonProcessingException;
    
    /**
     * @since 1.7
     */
    public final void defaultSerializeNull(JsonGenerator jgen)
        throws IOException, JsonProcessingException
    {
        getNullValueSerializer().serialize(null, jgen, this);
    }
    
    /*
    /********************************************************
    /* Access to caching details
    /********************************************************
     */

    /**
     * Method that can be used to determine how many serializers this
     * provider is caching currently
     * (if it does caching: default implementation does)
     * Exact count depends on what kind of serializers get cached;
     * default implementation caches all serializers, including ones that
     * are eagerly constructed (for optimal access speed)
     *<p> 
     * The main use case for this method is to allow conditional flushing of
     * serializer cache, if certain number of entries is reached.
     * 
     * @since 1.4
     */
    public abstract int cachedSerializersCount();

    /**
     * Method that will drop all serializers currently cached by this provider.
     * This can be used to remove memory usage (in case some serializers are
     * only used once or so), or to force re-construction of serializers after
     * configuration changes for mapper than owns the provider.
     * 
     * @since 1.4
     */
    public abstract void flushCachedSerializers();
}
