| package org.codehaus.jackson.map.ser.impl; |
| |
| import java.util.*; |
| |
| import org.codehaus.jackson.type.JavaType; |
| import org.codehaus.jackson.map.JsonMappingException; |
| import org.codehaus.jackson.map.JsonSerializer; |
| import org.codehaus.jackson.map.ResolvableSerializer; |
| import org.codehaus.jackson.map.SerializerProvider; |
| |
| /** |
| * Simple cache object that allows for doing 2-level lookups: first level is |
| * by "local" read-only lookup Map (used without locking) |
| * and second backup level is by a shared modifiable HashMap. |
| * The idea is that after a while, most serializers are found from the |
| * local Map (to optimize performance, reduce lock contention), |
| * but that during buildup we can use a shared map to reduce both |
| * number of distinct read-only maps constructed, and number of |
| * serializers constructed. |
| *<p> |
| * Since version 1.5 cache will actually contain three kinds of entries, |
| * based on combination of class pair key. First class in key is for the |
| * type to serialize, and second one is type used for determining how |
| * to resolve value type. One (but not both) of entries can be null. |
| */ |
| public final class SerializerCache |
| { |
| /** |
| * Shared, modifiable map; all access needs to be through synchronized blocks. |
| *<p> |
| * NOTE: keys are of various types (see below for key types), in addition to |
| * basic {@link JavaType} used for "untyped" serializers. |
| */ |
| private HashMap<TypeKey, JsonSerializer<Object>> _sharedMap = new HashMap<TypeKey, JsonSerializer<Object>>(64); |
| |
| /** |
| * Most recent read-only instance, created from _sharedMap, if any. |
| */ |
| private ReadOnlyClassToSerializerMap _readOnlyMap = null; |
| |
| public SerializerCache() { |
| } |
| |
| /** |
| * Method that can be called to get a read-only instance populated from the |
| * most recent version of the shared lookup Map. |
| */ |
| public ReadOnlyClassToSerializerMap getReadOnlyLookupMap() |
| { |
| ReadOnlyClassToSerializerMap m; |
| synchronized (this) { |
| m = _readOnlyMap; |
| if (m == null) { |
| _readOnlyMap = m = ReadOnlyClassToSerializerMap.from(_sharedMap); |
| } |
| } |
| return m.instance(); |
| } |
| |
| /* |
| /********************************************************** |
| /* Lookup methods for accessing shared (slow) cache |
| /********************************************************** |
| */ |
| |
| /** |
| * @since 1.4 |
| */ |
| public synchronized int size() { |
| return _sharedMap.size(); |
| } |
| |
| /** |
| * Method that checks if the shared (and hence, synchronized) lookup Map might have |
| * untyped serializer for given type. |
| */ |
| public JsonSerializer<Object> untypedValueSerializer(Class<?> type) |
| { |
| synchronized (this) { |
| return _sharedMap.get(new TypeKey(type, false)); |
| } |
| } |
| |
| /** |
| * @since 1.5 |
| */ |
| public JsonSerializer<Object> untypedValueSerializer(JavaType type) |
| { |
| synchronized (this) { |
| return _sharedMap.get(new TypeKey(type, false)); |
| } |
| } |
| |
| public JsonSerializer<Object> typedValueSerializer(JavaType type) |
| { |
| synchronized (this) { |
| return _sharedMap.get(new TypeKey(type, true)); |
| } |
| } |
| |
| public JsonSerializer<Object> typedValueSerializer(Class<?> cls) |
| { |
| synchronized (this) { |
| return _sharedMap.get(new TypeKey(cls, true)); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Methods for adding shared serializer instances |
| /********************************************************** |
| */ |
| |
| /** |
| * Method called if none of lookups succeeded, and caller had to construct |
| * a serializer. If so, we will update the shared lookup map so that it |
| * can be resolved via it next time. |
| */ |
| public void addTypedSerializer(JavaType type, JsonSerializer<Object> ser) |
| { |
| synchronized (this) { |
| if (_sharedMap.put(new TypeKey(type, true), ser) == null) { |
| // let's invalidate the read-only copy, too, to get it updated |
| _readOnlyMap = null; |
| } |
| } |
| } |
| |
| public void addTypedSerializer(Class<?> cls, JsonSerializer<Object> ser) |
| { |
| synchronized (this) { |
| if (_sharedMap.put(new TypeKey(cls, true), ser) == null) { |
| // let's invalidate the read-only copy, too, to get it updated |
| _readOnlyMap = null; |
| } |
| } |
| } |
| |
| /** |
| * @since 1.8 |
| */ |
| public void addAndResolveNonTypedSerializer(Class<?> type, JsonSerializer<Object> ser, |
| SerializerProvider provider) |
| throws JsonMappingException |
| { |
| synchronized (this) { |
| if (_sharedMap.put(new TypeKey(type, false), ser) == null) { |
| // let's invalidate the read-only copy, too, to get it updated |
| _readOnlyMap = null; |
| } |
| /* Finally: some serializers want to do post-processing, after |
| * getting registered (to handle cyclic deps). |
| */ |
| /* 14-May-2011, tatu: As per [JACKSON-570], resolving needs to be done |
| * in synchronized manner; this because while we do need to register |
| * instance first, we also must keep lock until resolution is complete |
| */ |
| if (ser instanceof ResolvableSerializer) { |
| ((ResolvableSerializer) ser).resolve(provider); |
| } |
| } |
| } |
| |
| /** |
| * @since 1.8 |
| */ |
| public void addAndResolveNonTypedSerializer(JavaType type, JsonSerializer<Object> ser, |
| SerializerProvider provider) |
| throws JsonMappingException |
| { |
| synchronized (this) { |
| if (_sharedMap.put(new TypeKey(type, false), ser) == null) { |
| // let's invalidate the read-only copy, too, to get it updated |
| _readOnlyMap = null; |
| } |
| /* Finally: some serializers want to do post-processing, after |
| * getting registered (to handle cyclic deps). |
| */ |
| /* 14-May-2011, tatu: As per [JACKSON-570], resolving needs to be done |
| * in synchronized manner; this because while we do need to register |
| * instance first, we also must keep lock until resolution is complete |
| */ |
| if (ser instanceof ResolvableSerializer) { |
| ((ResolvableSerializer) ser).resolve(provider); |
| } |
| } |
| } |
| |
| /** |
| * Method called by StdSerializerProvider#flushCachedSerializers() to |
| * clear all cached serializers |
| */ |
| public synchronized void flush() { |
| _sharedMap.clear(); |
| } |
| |
| /* |
| /************************************************************** |
| /* Helper class(es) |
| /************************************************************** |
| */ |
| |
| /** |
| * Key that offers two "modes"; one with raw class, as used for |
| * cases were raw class type is available (for example, when using |
| * runtime type); and one with full generics-including. |
| */ |
| public final static class TypeKey |
| { |
| protected int _hashCode; |
| |
| protected Class<?> _class; |
| |
| protected JavaType _type; |
| |
| /** |
| * Indicator of whether serializer stored has a type serializer |
| * wrapper around it or not; if not, it is "untyped" serializer; |
| * if it has, it is "typed" |
| */ |
| protected boolean _isTyped; |
| |
| public TypeKey(Class<?> key, boolean typed) { |
| _class = key; |
| _type = null; |
| _isTyped = typed; |
| _hashCode = hash(key, typed); |
| } |
| |
| public TypeKey(JavaType key, boolean typed) { |
| _type = key; |
| _class = null; |
| _isTyped = typed; |
| _hashCode = hash(key, typed); |
| } |
| |
| private final static int hash(Class<?> cls, boolean typed) { |
| int hash = cls.getName().hashCode(); |
| if (typed) { |
| ++hash; |
| } |
| return hash; |
| } |
| |
| private final static int hash(JavaType type, boolean typed) { |
| int hash = type.hashCode() - 1; |
| if (typed) { |
| --hash; |
| } |
| return hash; |
| } |
| |
| public void resetTyped(Class<?> cls) { |
| _type = null; |
| _class = cls; |
| _isTyped = true; |
| _hashCode = hash(cls, true); |
| } |
| |
| public void resetUntyped(Class<?> cls) { |
| _type = null; |
| _class = cls; |
| _isTyped = false; |
| _hashCode = hash(cls, false); |
| } |
| |
| public void resetTyped(JavaType type) { |
| _type = type; |
| _class = null; |
| _isTyped = true; |
| _hashCode = hash(type, true); |
| } |
| |
| public void resetUntyped(JavaType type) { |
| _type = type; |
| _class = null; |
| _isTyped = false; |
| _hashCode = hash(type, false); |
| } |
| |
| @Override public final int hashCode() { return _hashCode; } |
| |
| @Override public final String toString() { |
| if (_class != null) { |
| return "{class: "+_class.getName()+", typed? "+_isTyped+"}"; |
| } |
| return "{type: "+_type+", typed? "+_isTyped+"}"; |
| } |
| |
| // note: we assume key is never used for anything other than as map key, so: |
| @Override public final boolean equals(Object o) |
| { |
| if (o == this) return true; |
| TypeKey other = (TypeKey) o; |
| if (other._isTyped == _isTyped) { |
| if (_class != null) { |
| return other._class == _class; |
| } |
| return _type.equals(other._type); |
| } |
| return false; |
| } |
| } |
| } |