package org.codehaus.jackson.map.ser;
import java.lang.reflect.Modifier;
import java.util.*;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.type.ClassKey;
/**
* Serializer factory implementation that allows for configuring
* mapping between types (classes) and serializers to use, by using
* multiple types of overrides. Existing mappings established by
* {@link BeanSerializerFactory} (and its super class,
* {@link BasicSerializerFactory}) are used if no overrides are
* defined.
*<p>
* Unlike base serializer factories ({@link BasicSerializerFactory},
* {@link BeanSerializerFactory}), this factory is stateful because
* of configuration settings. It is thread-safe, however, as long as
* all configuration as done before using the factory -- a single
* instance can be shared between providers and mappers.
*<p>
* Configurations currently available are:
*<ul>
* <li>Ability to define explicit mappings between classes and interfaces
* and serializers to use. These can be either specific ones (class must
* match exactly) or generic ones (any sub-class or class implementing
* the interface); specific ones have precedence over generic ones (and
* precedence between generic ones is not defined).
* </li>
* <li>Ability to define a single generic base serializer for all Enum
* types (precedence below specific serializer mapping)
* </li>
*</ul>
*<p>
* In near future, following features are planned to be added:
*<ul>
* <li>Ability to define "mix-in annotations": associations between types
* (classes, interfaces) to serialize, and a "mix-in" type which will
* be used so that all of its annotations are added to the serialized
* type. Mixed-in annotations have priority over annotations that the
* serialized type has. In effect this allows for overriding annotations
* types have; this is useful when type definition itself can not be
* modified
* </li>
*</ul>
*/
public class CustomSerializerFactory
extends BeanSerializerFactory
{
/*
////////////////////////////////////////////////////
// Configuration, direct/special mappings
////////////////////////////////////////////////////
*/
/**
* Direct mappings that are only used for exact class type
* matches, but not for sub-class checks.
*/
HashMap<ClassKey,JsonSerializer<?>> _directClassMappings = null;
/**
* And for Enum handling we may specify a single default
* serializer to use, regardless of actual enumeration.
* Usually used to provide "toString - serializer".
*/
JsonSerializer<?> _enumSerializerOverride;
/*
////////////////////////////////////////////////////
// Configuration, generic (interface, super-class) mappings
////////////////////////////////////////////////////
*/
/**
* And then class-based mappings that are used both for exact and
* sub-class matches.
*/
HashMap<ClassKey,JsonSerializer<?>> _transitiveClassMappings = null;
/**
* And finally interface-based matches.
*/
HashMap<ClassKey,JsonSerializer<?>> _interfaceMappings = null;
/*
//////////////////////////////////////////////////////////
// Configuration: "mix-in annotation" mappings
//////////////////////////////////////////////////////////
*/
/**
* Mapping that defines how to apply mix-in annotations: key is
* the type to received additional annotations, and value is the
* type that has annotations to "mix in".
*<p>
* !!! 30-Mar-2009, tatu: Not used as of yet
*/
HashMap<ClassKey,Class<?>> _mixInAnnotations;
/*
////////////////////////////////////////////////////
// Life-cycle, constructors
////////////////////////////////////////////////////
*/
public CustomSerializerFactory() {
super();
}
/*
////////////////////////////////////////////////////
// Configuration: type-to-serializer mappings
////////////////////////////////////////////////////
*/
/**
* Method used to add a generic (transitive) mapping from specified
* class or its sub-classes into a serializer.
* When resolving a type into a serializer, explicit class is checked
* first, then immediate super-class, and so forth along inheritance
* chain. But if this fails, implemented interfaces are checked;
* ordering is done such that first interfaces implemented by
* the exact type are checked (in order returned by
* {@link Class#getInterfaces}), then super-type's and so forth.
*<p>
* Note that adding generic mappings may lead to problems with
* sub-classing: if sub-classes add new properties, these may not
* get properly serialized.
*
* @param type Class for which specified serializer is to be
* used. May be more specific type than what serializer indicates,
* but must be compatible (same or sub-class)
*/
public <T> void addGenericMapping(Class<? extends T> type, JsonSerializer<T> ser)
{
// Interface to match?
ClassKey key = new ClassKey(type);
if (type.isInterface()) {
if (_interfaceMappings == null) {
_interfaceMappings = new HashMap<ClassKey,JsonSerializer<?>>();
}
_interfaceMappings.put(key, ser);
} else { // nope, class:
if (_transitiveClassMappings == null) {
_transitiveClassMappings = new HashMap<ClassKey,JsonSerializer<?>>();
}
_transitiveClassMappings.put(key, ser);
}
}
/**
* Method used to add a mapping from specific type -- and only that
* type -- to specified serializer. This means that binding is not
* used for sub-types. It also means that no such mappings are to
* be defined for abstract classes or interfaces: and if an attempt
* is made, {@link IllegalArgumentException} will be thrown to
* indicate caller error.
*
* @param forClass Class for which specified serializer is to be
* used. May be more specific type than what serializer indicates,
* but must be compatible (same or sub-class)
*/
public <T> void addSpecificMapping(Class<? extends T> forClass, JsonSerializer<T> ser)
{
ClassKey key = new ClassKey(forClass);
/* First, let's ensure it's not an interface or abstract class:
* as those can not be instantiated, such mappings would never
* get used.
*/
if (forClass.isInterface()) {
throw new IllegalArgumentException("Can not add specific mapping for an interface ("+forClass.getName()+")");
}
if (Modifier.isAbstract(forClass.getModifiers())) {
throw new IllegalArgumentException("Can not add specific mapping for an abstract class ("+forClass.getName()+")");
}
if (_directClassMappings == null) {
_directClassMappings = new HashMap<ClassKey,JsonSerializer<?>>();
}
_directClassMappings.put(key, ser);
}
/**
* Method that can be used to force specified serializer to be used for
* serializing all Enum instances. This is most commonly used to specify
* serializers that call either <code>enum.toString()</code>, or modify
* value returned by <code>enum.name()</code> (such as upper- or
* lower-casing it).
*<p>
* Note: this serializer has lower precedence than that of specific
* types; so if a specific serializer is assigned to an Enum type,
* this serializer will NOT be used. It has higher precedence than
* generic mappings have however.
*/
public void setEnumSerializer(JsonSerializer<?> enumSer)
{
_enumSerializerOverride = enumSer;
}
/**
* Method to use for adding mix-in annotations that Class
* <code>classWithMixIns</code> contains into class
* <code>destinationClass</code>. Mixing in is done when introspecting
* class annotations and properties.
* Annotations from <code>classWithMixIns</code> (and its supertypes)
* will <b>override</b>
* anything <code>destinationClass</code> (and its super-types)
* has already.
*
* @param destinationClass Type to modify by adding annotations
* @param classWithMixIns Type that contains annotations to add
*/
public void addMixInAnnotationMapping(Class<?> destinationClass,
Class<?> classWithMixIns)
{
if (_mixInAnnotations == null) {
_mixInAnnotations = new HashMap<ClassKey,Class<?>>();
}
_mixInAnnotations.put(new ClassKey(destinationClass), classWithMixIns);
}
/*
////////////////////////////////////////////////////
// JsonSerializerFactory impl
////////////////////////////////////////////////////
*/
@Override
@SuppressWarnings("unchecked")
public <T> JsonSerializer<T> createSerializer(Class<T> type, SerializationConfig config)
{
JsonSerializer<?> ser = null;
ClassKey key = new ClassKey(type);
// First: exact matches
if (_directClassMappings != null) {
ser = _directClassMappings.get(key);
if (ser != null) {
return (JsonSerializer<T>) ser;
}
}
// No match? Perhaps we can use the enum serializer?
if (type.isEnum()) {
if (_enumSerializerOverride != null) {
return (JsonSerializer<T>) _enumSerializerOverride;
}
}
// Still no match? How about more generic ones?
// Mappings for super-classes?
if (_transitiveClassMappings != null) {
for (Class<?> curr = type; (curr != null); curr = curr.getSuperclass()) {
key.reset(curr);
ser = _transitiveClassMappings.get(key);
if (ser != null) {
return (JsonSerializer<T>) ser;
}
}
}
// And if still no match, how about interfaces?
if (_interfaceMappings != null) {
for (Class<?> curr = type; (curr != null); curr = curr.getSuperclass()) {
for (Class<?> iface : curr.getInterfaces()) {
key.reset(iface);
ser = _interfaceMappings.get(key);
if (ser != null) {
return (JsonSerializer<T>) ser;
}
}
}
}
/* And barring any other complications, let's just let
* bean (or basic) serializer factory handle construction.
*/
return super.createSerializer(type, config);
}
}