diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java index e85638dd2f..b6ded4f4bb 100644 --- a/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java +++ b/src/main/java/com/fasterxml/jackson/databind/deser/DeserializerCache.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.type.*; import com.fasterxml.jackson.databind.util.ClassUtil; import com.fasterxml.jackson.databind.util.Converter; +import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.SimpleLookupCache; /** @@ -41,7 +42,7 @@ public final class DeserializerCache * This currently (3.0) means POJO, Enum and Container (collection, * map) deserializers. */ - private final SimpleLookupCache> _cachedDeserializers; + private final LookupCache> _cachedDeserializers; /** * During deserializer construction process we may need to keep track of partially diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java index b31ff8ec24..a74c456569 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/BasicClassIntrospector.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.SimpleLookupCache; public class BasicClassIntrospector @@ -60,13 +61,13 @@ public class BasicClassIntrospector * because {@link #forMapper(Object)} initializes it properly, when mapper get * constructed. */ - protected final transient SimpleLookupCache _cachedFCA; + protected final transient LookupCache _cachedFCA; public BasicClassIntrospector() { this(null); } - protected BasicClassIntrospector(SimpleLookupCache cache) { + protected BasicClassIntrospector(LookupCache cache) { _cachedFCA = cache; } diff --git a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java index 2811122002..27b2910281 100644 --- a/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java +++ b/src/main/java/com/fasterxml/jackson/databind/introspect/JacksonAnnotationIntrospector.java @@ -78,7 +78,7 @@ public class JacksonAnnotationIntrospector *

* Non-final only because it needs to be re-created after deserialization. */ - protected transient SimpleLookupCache,Boolean> _annotationsInside = new SimpleLookupCache,Boolean>(48, 96); + protected transient LookupCache,Boolean> _annotationsInside = new SimpleLookupCache,Boolean>(48, 96); /* /********************************************************************** diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java b/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java index 88a71a7938..be4eaa2342 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/SerializerCache.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.util.Snapshottable; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.ser.impl.ReadOnlyClassToSerializerMap; +import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.SimpleLookupCache; import com.fasterxml.jackson.databind.util.TypeKey; @@ -40,7 +41,7 @@ public final class SerializerCache * NOTE: keys are of various types (see below for key types), in addition to * basic {@link JavaType} used for "untyped" serializers. */ - private final SimpleLookupCache> _sharedMap; + private final LookupCache> _sharedMap; /** * Most recent read-only instance, created from _sharedMap, if any. @@ -60,7 +61,7 @@ public SerializerCache(int maxCached) { _readOnlyMap = new AtomicReference(); } - private SerializerCache(SimpleLookupCache> shared) { + protected SerializerCache(LookupCache> shared) { _sharedMap = shared; _readOnlyMap = new AtomicReference(); } diff --git a/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java b/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java index ea3e12f873..9d795e419a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java +++ b/src/main/java/com/fasterxml/jackson/databind/ser/impl/ReadOnlyClassToSerializerMap.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ser.SerializerCache; -import com.fasterxml.jackson.databind.util.SimpleLookupCache; +import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.TypeKey; /** @@ -27,7 +27,7 @@ public final class ReadOnlyClassToSerializerMap private final int _mask; protected ReadOnlyClassToSerializerMap(SerializerCache shared, - SimpleLookupCache> src) + LookupCache> src) { _sharedCache = shared; _size = findSize(src.size()); @@ -55,7 +55,7 @@ private final static int findSize(int size) * Factory method for constructing an instance. */ public static ReadOnlyClassToSerializerMap from(SerializerCache shared, - SimpleLookupCache> src) { + LookupCache> src) { return new ReadOnlyClassToSerializerMap(shared, src); } diff --git a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java index 09ca46986a..26616684b4 100644 --- a/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java +++ b/src/main/java/com/fasterxml/jackson/databind/type/TypeFactory.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.util.ArrayBuilders; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.fasterxml.jackson.databind.util.LookupCache; import com.fasterxml.jackson.databind.util.SimpleLookupCache; /** @@ -121,7 +122,7 @@ public final class TypeFactory * actual generic types), we will use small cache to avoid repetitive * resolution of core types */ - protected final SimpleLookupCache _typeCache; + protected final LookupCache _typeCache; /* /********************************************************************** @@ -150,7 +151,7 @@ private TypeFactory() { this(null); } - protected TypeFactory(SimpleLookupCache typeCache) { + protected TypeFactory(LookupCache typeCache) { if (typeCache == null) { typeCache = new SimpleLookupCache(16, 200); } @@ -159,7 +160,7 @@ protected TypeFactory(SimpleLookupCache typeCache) { _classLoader = null; } - protected TypeFactory(SimpleLookupCache typeCache, + protected TypeFactory(LookupCache typeCache, TypeModifier[] mods, ClassLoader classLoader) { if (typeCache == null) { @@ -190,7 +191,7 @@ public TypeFactory snapshot() { */ public TypeFactory withModifier(TypeModifier mod) { - SimpleLookupCache typeCache = _typeCache; + LookupCache typeCache = _typeCache; TypeModifier[] mods; if (mod == null) { // mostly for unit tests mods = null; @@ -222,7 +223,7 @@ public TypeFactory withClassLoader(ClassLoader classLoader) { * identical settings except for different cache; most likely one with * bigger maximum size. */ - public TypeFactory withCache(SimpleLookupCache cache) { + public TypeFactory withCache(LookupCache cache) { return new TypeFactory(cache, _modifiers, _classLoader); } diff --git a/src/main/java/com/fasterxml/jackson/databind/util/LookupCache.java b/src/main/java/com/fasterxml/jackson/databind/util/LookupCache.java new file mode 100644 index 0000000000..70342fa803 --- /dev/null +++ b/src/main/java/com/fasterxml/jackson/databind/util/LookupCache.java @@ -0,0 +1,32 @@ +package com.fasterxml.jackson.databind.util; + +import com.fasterxml.jackson.core.util.Snapshottable; + +import java.util.function.BiConsumer; + +/** + * An interface describing the required API for the Jackson-Databind Type cache. + * + * @see com.fasterxml.jackson.databind.type.TypeFactory#withCache(LookupCache) + * @see SimpleLookupCache + */ +public interface LookupCache + extends Snapshottable> { + + void contents(BiConsumer consumer); + + int size(); + + /** + * NOTE: key is of type Object only to retain binary backwards-compatibility + * @param key + * @return value associated with key (can return null) + */ + V get(Object key); + + V put(K key, V value); + + V putIfAbsent(K key, V value); + + void clear(); +} diff --git a/src/main/java/com/fasterxml/jackson/databind/util/RootNameLookup.java b/src/main/java/com/fasterxml/jackson/databind/util/RootNameLookup.java index 5b0c46a14a..286a6ec586 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/RootNameLookup.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/RootNameLookup.java @@ -16,7 +16,7 @@ public class RootNameLookup implements java.io.Serializable * For efficient operation, let's try to minimize number of times we * need to introspect root element name to use. */ - protected transient SimpleLookupCache _rootNames; + protected transient LookupCache _rootNames; public RootNameLookup() { _rootNames = new SimpleLookupCache(20, 200); diff --git a/src/main/java/com/fasterxml/jackson/databind/util/SimpleLookupCache.java b/src/main/java/com/fasterxml/jackson/databind/util/SimpleLookupCache.java index b9f79eaa07..5ca0852272 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/SimpleLookupCache.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/SimpleLookupCache.java @@ -4,8 +4,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; -import com.fasterxml.jackson.core.util.Snapshottable; - /** * Synchronized cache with bounded size: used for reusing lookup values * and lazily instantiated reusable items. @@ -26,8 +24,7 @@ * a shaded variant may be used one day. */ public class SimpleLookupCache - implements Snapshottable>, - java.io.Serializable + implements LookupCache, java.io.Serializable { private static final long serialVersionUID = 3L; @@ -55,10 +52,11 @@ protected Object readResolve() { } @Override - public SimpleLookupCache snapshot() { + public LookupCache snapshot() { return new SimpleLookupCache(_initialEntries, _maxEntries); } + @Override public void contents(BiConsumer consumer) { for (Map.Entry entry : _map.entrySet()) { consumer.accept(entry.getKey(), entry.getValue()); @@ -70,7 +68,7 @@ public void contents(BiConsumer consumer) { /* Public API /********************************************************************** */ - + @Override public V put(K key, V value) { if (_map.size() >= _maxEntries) { // double-locking, yes, but safe here; trying to avoid "clear storms" @@ -83,6 +81,7 @@ public V put(K key, V value) { return _map.put(key, value); } + @Override public V putIfAbsent(K key, V value) { // not 100% optimal semantically, but better from correctness (never exceeds // defined maximum) and close enough all in all: @@ -96,9 +95,12 @@ public V putIfAbsent(K key, V value) { return _map.putIfAbsent(key, value); } - // NOTE: key is of type Object only to retain binary backwards-compatibility + @Override public V get(Object key) { return _map.get(key); } + @Override public void clear() { _map.clear(); } + + @Override public int size() { return _map.size(); } } diff --git a/src/test/java/com/fasterxml/jackson/databind/util/UnlimitedLookupCache.java b/src/test/java/com/fasterxml/jackson/databind/util/UnlimitedLookupCache.java new file mode 100644 index 0000000000..20f1955b86 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/util/UnlimitedLookupCache.java @@ -0,0 +1,60 @@ +package com.fasterxml.jackson.databind.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; + +/** + * A LookupCache implementation that has no synchronization (like SimpleLookupCache does) + * but that has the downside of not limiting the size of the cache. + */ +public class UnlimitedLookupCache implements LookupCache { + + private final int _initialEntries, _maxEntries; + private final transient ConcurrentHashMap _map; + + public UnlimitedLookupCache(int initialEntries, int maxEntries) + { + _initialEntries = initialEntries; + _maxEntries = maxEntries; + // We'll use concurrency level of 4, seems reasonable + _map = new ConcurrentHashMap(initialEntries, 0.8f, 4); + } + + @Override + public LookupCache snapshot() { + return new UnlimitedLookupCache(_initialEntries, _maxEntries); + } + + @Override + public void contents(BiConsumer consumer) { + for (Map.Entry entry : _map.entrySet()) { + consumer.accept(entry.getKey(), entry.getValue()); + } + } + + @Override + public int size() { + return _map.size(); + } + + @Override + public V get(Object key) { + return _map.get(key); + } + + @Override + public V put(K key, V value) { + return _map.put(key, value); + } + + @Override + public V putIfAbsent(K key, V value) { + return _map.putIfAbsent(key, value); + } + + @Override + public void clear() { + _map.clear(); + } +} diff --git a/src/test/java/com/fasterxml/jackson/databind/util/UnlimitedLookupCacheTest.java b/src/test/java/com/fasterxml/jackson/databind/util/UnlimitedLookupCacheTest.java new file mode 100644 index 0000000000..353e5238ea --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/util/UnlimitedLookupCacheTest.java @@ -0,0 +1,38 @@ +package com.fasterxml.jackson.databind.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class UnlimitedLookupCacheTest { + @Test + public void testCache() { + UnlimitedLookupCache cache = new UnlimitedLookupCache<>(4, 10); + assertNull(cache.get(1000L)); + assertNull(cache.put(1000L, "Thousand")); + assertEquals("Thousand", cache.get(1000L)); + assertEquals("Thousand", cache.putIfAbsent(1000L, "Míle")); + assertEquals("Thousand", cache.get(1000L)); + assertEquals("Thousand", cache.put(1000L, "Míle")); + assertEquals("Míle", cache.get(1000L)); + cache.clear(); + assertNull(cache.put(1000L, "Thousand")); + } + + @Test + public void testCompatibility() throws JsonProcessingException { + UnlimitedLookupCache cache = new UnlimitedLookupCache<>(4, 10); + TypeFactory tf = TypeFactory.defaultInstance().withCache(cache); + + //TODO find way to inject the `tf` instance into an ObjectMapper (via MapperBuilder?) + + //ObjectMapper mapper = new ObjectMapper(); + //mapper.setTypeFactory(tf); + //assertEquals("1000", mapper.writeValueAsString(1000)); + } +}