working on serializer refactoring.
diff --git a/build.xml b/build.xml index dfefb44..e8b4181 100644 --- a/build.xml +++ b/build.xml
@@ -207,12 +207,14 @@ ,org.codehaus.jackson.io;version=${IMPL_VERSION} ,org.codehaus.jackson.map;version=${IMPL_VERSION} ,org.codehaus.jackson.map.node;version=${IMPL_VERSION} +,org.codehaus.jackson.map.ser;version=${IMPL_VERSION} ' privatePackage="!test ,!org.codehaus.jackson ,!org.codehaus.jackson.io ,!org.codehaus.jackson.map ,!org.codehaus.jackson.map.node +,!org.codehaus.jackson.map.ser ,*" includeResource="META-INF=release-notes/asl" includeIncludeResourceHeader="false"
diff --git a/src/java/org/codehaus/jackson/map/JsonSerializer.java b/src/java/org/codehaus/jackson/map/JsonSerializer.java index 88d8053..257d2aa 100644 --- a/src/java/org/codehaus/jackson/map/JsonSerializer.java +++ b/src/java/org/codehaus/jackson/map/JsonSerializer.java
@@ -16,7 +16,7 @@ * values of type this serializer handles. * * @param value Value to serialize - * @param gen Generator used to output resulting Json content + * @param jgen Generator used to output resulting Json content * @param provider Provider that can be used to get serializers for * serializing Objects value contains, if any. */
diff --git a/src/java/org/codehaus/jackson/map/JsonSerializerFactory.java b/src/java/org/codehaus/jackson/map/JsonSerializerFactory.java index 6e600ae..623bb36 100644 --- a/src/java/org/codehaus/jackson/map/JsonSerializerFactory.java +++ b/src/java/org/codehaus/jackson/map/JsonSerializerFactory.java
@@ -13,4 +13,3 @@ */ public abstract <T> JsonSerializer<T> createSerializer(Class<T> type); } -
diff --git a/src/java/org/codehaus/jackson/map/JsonSerializerProvider.java b/src/java/org/codehaus/jackson/map/JsonSerializerProvider.java index 007616c..6d8d101 100644 --- a/src/java/org/codehaus/jackson/map/JsonSerializerProvider.java +++ b/src/java/org/codehaus/jackson/map/JsonSerializerProvider.java
@@ -32,14 +32,15 @@ /** * Method called to get the serializer to use for serializing - * Map keys that are not nulls (for null keys, - * {@link #getNullKeySerializer} is called instead). - * Separation from regular - * {@link #findSerializer} method is due to Json only allowing - * Json Strings as field names; and hence different serialization - * strategy may be needed. + * 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. */ - public abstract JsonSerializer<?> findNonNullKeySerializer(Class<?> type); + public abstract JsonSerializer<Object> getKeySerializer(); /** * Method called to get the serializer to use for serializing @@ -50,7 +51,7 @@ * will either throw an exception, or use an empty String; but * other behaviors are possible. */ - public abstract JsonSerializer<?> getNullKeySerializer(); + public abstract JsonSerializer<Object> getNullKeySerializer(); /** * Method called to get the serializer to use for serializing @@ -61,5 +62,5 @@ * Typically returned serializer just writes out Json literal * null value. */ - public abstract JsonSerializer<?> getNullValueSerializer(); + public abstract JsonSerializer<Object> getNullValueSerializer(); }
diff --git a/src/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java b/src/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java index fe0ac2d..14c0e30 100644 --- a/src/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java +++ b/src/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
@@ -1,13 +1,6 @@ package org.codehaus.jackson.map.ser; -import java.io.IOException; -import java.util.*; - -import org.codehaus.jackson.JsonGenerationException; -import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.JsonSerializer; -import org.codehaus.jackson.map.JsonSerializerFactory; -import org.codehaus.jackson.map.JsonSerializerProvider; /** * Factory class that can provide serializers for any regular Java beans
diff --git a/src/java/org/codehaus/jackson/map/ser/FailingSerializer.java b/src/java/org/codehaus/jackson/map/ser/FailingSerializer.java new file mode 100644 index 0000000..c3e2dda --- /dev/null +++ b/src/java/org/codehaus/jackson/map/ser/FailingSerializer.java
@@ -0,0 +1,29 @@ +package org.codehaus.jackson.map.ser; + +import java.io.IOException; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.JsonSerializerProvider; + +/** + * Special bogus "serializer" that will throw + * {@link JsonGenerationException} if its {@link #serialize} + * gets invoeked. Most commonly registered as handler for unknown types, + * as well as for catching unintended usage (like trying to use null + * as Map/Object key). + */ +public final class FailingSerializer + extends JsonSerializer<Object> +{ + final String _msg; + + public FailingSerializer(String msg) { _msg = msg; } + + public void serialize(Object value, JsonGenerator jgen, JsonSerializerProvider provider) + throws IOException, JsonGenerationException + { + throw new JsonGenerationException(_msg); + } +}
diff --git a/src/java/org/codehaus/jackson/map/ser/SerializerProviderBase.java b/src/java/org/codehaus/jackson/map/ser/SerializerProviderBase.java index 52b8923..6bba0b4 100644 --- a/src/java/org/codehaus/jackson/map/ser/SerializerProviderBase.java +++ b/src/java/org/codehaus/jackson/map/ser/SerializerProviderBase.java
@@ -1,14 +1,13 @@ package org.codehaus.jackson.map.ser; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +//import java.util.concurrent.ConcurrentHashMap; import org.codehaus.jackson.map.JsonSerializer; import org.codehaus.jackson.map.JsonSerializerProvider; /** * Abstract base class that defines lowest-level common feature of most - * implementations of {@JsonSerializerProvider}: that of locally + * implementations of {@link JsonSerializerProvider}: that of locally * storing references to serializers once they have been succesfully * constructed. */ @@ -24,13 +23,13 @@ } @Override - public abstract JsonSerializer<?> findNonNullKeySerializer(Class<?> type); + public abstract JsonSerializer<Object> getKeySerializer(); @Override - public abstract JsonSerializer<?> getNullKeySerializer(); + public abstract JsonSerializer<Object> getNullKeySerializer(); @Override - public abstract JsonSerializer<?> getNullValueSerializer(); + public abstract JsonSerializer<Object> getNullValueSerializer(); /* //////////////////////////////////////////////////
diff --git a/src/java/org/codehaus/jackson/map/ser/StdKeySerializer.java b/src/java/org/codehaus/jackson/map/ser/StdKeySerializer.java new file mode 100644 index 0000000..0c524ea --- /dev/null +++ b/src/java/org/codehaus/jackson/map/ser/StdKeySerializer.java
@@ -0,0 +1,27 @@ +package org.codehaus.jackson.map.ser; + +import java.io.IOException; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.JsonSerializerProvider; + +/** + * Specialized serializer that can be used as the generic key + * serializer, when serializing {@link java.util.Map}s to Json + * Objects. + */ +public final class StdKeySerializer + extends JsonSerializer<Object> +{ + final static StdKeySerializer instace = new StdKeySerializer(); + + public void serialize(Object value, JsonGenerator jgen, JsonSerializerProvider provider) + throws IOException, JsonGenerationException + { + String keyStr = (value.getClass() == String.class) ? + ((String) value) : value.toString(); + jgen.writeFieldName(keyStr); + } +}
diff --git a/src/java/org/codehaus/jackson/map/ser/StdSerializerFactory.java b/src/java/org/codehaus/jackson/map/ser/StdSerializerFactory.java index 9e2c288..60988fb 100644 --- a/src/java/org/codehaus/jackson/map/ser/StdSerializerFactory.java +++ b/src/java/org/codehaus/jackson/map/ser/StdSerializerFactory.java
@@ -39,11 +39,12 @@ // String and string-like types (note: date types explicitly // not included -- can use either textual or numeric serialization) _concrete.put(String.class.getName(), new StringSerializer()); - _concrete.put(StringBuffer.class.getName(), StringLikeSerializer.instance); - _concrete.put(StringBuilder.class.getName(), StringLikeSerializer.instance); - _concrete.put(Character.class.getName(), StringLikeSerializer.instance); + ToStringSerializer sls = ToStringSerializer.instance; + _concrete.put(StringBuffer.class.getName(), sls); + _concrete.put(StringBuilder.class.getName(), sls); + _concrete.put(Character.class.getName(), sls); // including things best serialized as Strings - _concrete.put(UUID.class.getName(), StringLikeSerializer.instance); + _concrete.put(UUID.class.getName(), sls); // Numbers, limited length integral final IntegerSerializer intS = new IntegerSerializer(); @@ -89,13 +90,16 @@ _concrete.put(Hashtable.class.getName(), mapS); _concrete.put(LinkedHashMap.class.getName(), mapS); _concrete.put(TreeMap.class.getName(), mapS); - _concrete.put(EnumMap.class.getName(), mapS); _concrete.put(Properties.class.getName(), mapS); _concrete.put(HashSet.class.getName(), collectionS); _concrete.put(LinkedHashSet.class.getName(), collectionS); _concrete.put(TreeSet.class.getName(), collectionS); + // and Enum-variations of set/map + _concrete.put(EnumMap.class.getName(), new EnumMapSerializer()); + _concrete.put(EnumSet.class.getName(), new EnumSetSerializer()); + /* Finally, couple of oddball types. Not sure if these are * really needed... */ @@ -206,12 +210,15 @@ } return CollectionSerializer.instance; } - if (Collection.class.isAssignableFrom(type)) { - return CollectionSerializer.instance; - } if (Number.class.isAssignableFrom(type)) { return NumberSerializer.instance; } + if (Enum.class.isAssignableFrom(type)) { + return new EnumSerializer(); + } + if (Collection.class.isAssignableFrom(type)) { + return CollectionSerializer.instance; + } return null; } @@ -514,6 +521,22 @@ } } + public final static class EnumSetSerializer + extends JsonSerializer<EnumSet<? extends Enum<?>>> + { + public final static CollectionSerializer instance = new CollectionSerializer(); + + public void serialize(EnumSet<? extends Enum<?>> value, JsonGenerator jgen, JsonSerializerProvider provider) + throws IOException, JsonGenerationException + { + jgen.writeStartArray(); + for (Enum<?> en : value) { + jgen.writeString(en.name()); + } + jgen.writeEndArray(); + } + } + /* //////////////////////////////////////////////////////////// // Concrete serializers, Maps @@ -534,9 +557,8 @@ final int len = value.size(); if (len > 0) { - JsonSerializer<Object> prevKeySerializer = null; + final JsonSerializer<Object> keySerializer = provider.getKeySerializer(); JsonSerializer<Object> prevValueSerializer = null; - Class<?> prevKeyClass = null; Class<?> prevValueClass = null; for (Map.Entry<?,?> entry : value.entrySet()) { @@ -545,16 +567,7 @@ if (keyElem == null) { provider.getNullKeySerializer().serialize(null, jgen, provider); } else { - Class<?> cc = keyElem.getClass(); - JsonSerializer<Object> currSerializer; - if (cc == prevKeyClass) { - currSerializer = prevKeySerializer; - } else { - currSerializer = (JsonSerializer<Object>)provider.findNonNullKeySerializer(cc); - prevKeySerializer = currSerializer; - prevKeyClass = cc; - } - currSerializer.serialize(keyElem, jgen, provider); + keySerializer.serialize(keyElem, jgen, provider); } // And then value @@ -580,6 +593,42 @@ } } + public final static class EnumMapSerializer + extends JsonSerializer<EnumMap<? extends Enum<?>, ?>> + { + @SuppressWarnings("unchecked") + public void serialize(EnumMap<? extends Enum<?>,?> value, JsonGenerator jgen, JsonSerializerProvider provider) + throws IOException, JsonGenerationException + { + jgen.writeStartArray(); + JsonSerializer<Object> prevSerializer = null; + Class<?> prevClass = null; + + for (Map.Entry<? extends Enum<?>,?> entry : value.entrySet()) { + // First, serialize key + jgen.writeFieldName(entry.getKey().name()); + // And then value + Object valueElem = entry.getKey(); + if (valueElem == null) { + provider.getNullValueSerializer().serialize(null, jgen, provider); + } else { + Class<?> cc = valueElem.getClass(); + JsonSerializer<Object> currSerializer; + if (cc == prevClass) { + currSerializer = prevSerializer; + } else { + currSerializer = (JsonSerializer<Object>)provider.findValueSerializer(cc); + prevSerializer = currSerializer; + prevClass = cc; + } + currSerializer.serialize(valueElem, jgen, provider); + } + } + + jgen.writeEndArray(); + } + } + /* //////////////////////////////////////////////////////////// // Concrete serializers, arrays @@ -765,10 +814,20 @@ /* //////////////////////////////////////////////////////////// - // Other odd-ball special purpose serializers + // Other odd-ball special-purpose serializers //////////////////////////////////////////////////////////// */ + public final static class EnumSerializer + extends JsonSerializer<Enum<?>> + { + public void serialize(Enum<?> value, JsonGenerator jgen, JsonSerializerProvider provider) + throws IOException, JsonGenerationException + { + jgen.writeString(value.name()); + } + } + /** * To allow for special handling for null values (in Objects, Arrays, * root-level), handling for nulls is done via serializers too.
diff --git a/src/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java b/src/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java index 8da4729..163f340 100644 --- a/src/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java +++ b/src/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
@@ -1,8 +1,5 @@ package org.codehaus.jackson.map.ser; -import java.io.IOException; - -import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.JsonSerializer; @@ -20,12 +17,14 @@ public class StdSerializerProvider extends SerializerProviderBase { - public final static JsonSerializer<?> DEFAULT_NULL_KEY_SERIALIZER = + public final static JsonSerializer<Object> DEFAULT_NULL_KEY_SERIALIZER = new StdSerializerFactory.FailingSerializer("Null key for a Map not allower in Json (use a converting NullKeySerializer?)"); + public final static JsonSerializer<Object> DEFAULT_KEY_SERIALIZER = new StdKeySerializer(); + /* //////////////////////////////////////////////////// - // Configuration + // Configuration, factories //////////////////////////////////////////////////// */ @@ -41,20 +40,42 @@ */ protected JsonSerializerFactory _overrideSerializerFactory = null; + /* + //////////////////////////////////////////////////// + // Configuration, specialized serializers + //////////////////////////////////////////////////// + */ + + /** + * Serializer that gets called for values of types for which no + * serializers can be constructed. + *<p> + * The default serializer will thrown an exception; a possible + * alternative that can be used would be + * {@link ToStringSerializer}. + */ + protected JsonSerializer<Object> _unknownTypeSerializer; + + /** + * Serializer used to output non-null keys of Maps (which will get + * output as Json Objects). + */ + protected JsonSerializer<Object> _keySerializer = DEFAULT_KEY_SERIALIZER; + /** * Serializer used to output a null value. Default implementation * writes nulls using {@link JsonGenerator#writeNull}. */ - protected JsonSerializer<?> _nullValueSerializer = StdSerializerFactory.NullSerializer.instance; + protected JsonSerializer<Object> _nullValueSerializer = StdSerializerFactory.NullSerializer.instance; /** * Serializer used to (try to) output a null key, due to an entry of - * {@link Map} having null key. + * {@link java.util.Map} having null key. * The default implementation will throw an exception if this happens; * alternative implementation (like one that would write an Empty String) * can be defined. */ - protected JsonSerializer<?> _nullKeySerializer = DEFAULT_NULL_KEY_SERIALIZER; + protected JsonSerializer<Object> _nullKeySerializer = DEFAULT_NULL_KEY_SERIALIZER; /* //////////////////////////////////////////////////// @@ -86,7 +107,15 @@ _overrideSerializerFactory = jf; } - public void setNullValueSerializer(JsonSerializer<?> nvs) + public void setKeySerializer(JsonSerializer<Object> ks) + { + if (ks == null) { + throw new IllegalArgumentException("Can not pass null JsonSerializer"); + } + _keySerializer = ks; + } + + public void setNullValueSerializer(JsonSerializer<Object> nvs) { if (nvs == null) { throw new IllegalArgumentException("Can not pass null JsonSerializer"); @@ -94,7 +123,7 @@ _nullValueSerializer = nvs; } - public void setNullKeySerializer(JsonSerializer<?> nks) + public void setNullKeySerializer(JsonSerializer<Object> nks) { if (nks == null) { throw new IllegalArgumentException("Can not pass null JsonSerializer"); @@ -111,24 +140,35 @@ @Override protected JsonSerializer<?> constructValueSerializer(Class<?> type) { - // !!! TBI - return null; + if (_overrideSerializerFactory != null) { + JsonSerializer<?> ser = _overrideSerializerFactory.createSerializer(type); + if (ser != null) { + return ser; + } + } + JsonSerializer<?> ser = _serializerFactory.createSerializer(type); + if (ser != null) { + return ser; + } + /* No match yet? Need to return the fallback serializer, which + * most likely will report an error + */ + return _unknownTypeSerializer; } @Override - public final JsonSerializer<?> findNonNullKeySerializer(Class<?> type) + public final JsonSerializer<Object> getKeySerializer() { - // !!! TBI - return null; + return _keySerializer; } @Override - public final JsonSerializer<?> getNullKeySerializer() { + public final JsonSerializer<Object> getNullKeySerializer() { return _nullKeySerializer; } @Override - public final JsonSerializer<?> getNullValueSerializer() { + public final JsonSerializer<Object> getNullValueSerializer() { return _nullValueSerializer; } }
diff --git a/src/java/org/codehaus/jackson/map/ser/ToStringSerializer.java b/src/java/org/codehaus/jackson/map/ser/ToStringSerializer.java new file mode 100644 index 0000000..8186607 --- /dev/null +++ b/src/java/org/codehaus/jackson/map/ser/ToStringSerializer.java
@@ -0,0 +1,30 @@ +package org.codehaus.jackson.map.ser; + +import java.io.IOException; + +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.JsonSerializerProvider; + +/** + * Simple general purpose serializer, useful for any + * type for which {@link Object#toString} returns the desired Json + * value. + */ +public final class ToStringSerializer + extends JsonSerializer<Object> +{ + /** + * Singleton instance to use. + */ + public final static ToStringSerializer instance = new ToStringSerializer(); + + private ToStringSerializer() { } // no instantiation, use singleton + + public void serialize(Object value, JsonGenerator jgen, JsonSerializerProvider provider) + throws IOException, JsonGenerationException + { + jgen.writeString(value.toString()); + } +}