From c270234f37f0d9bdb1755f22561cbcba156b6e8c Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Wed, 18 Aug 2010 16:54:59 +0000 Subject: Introduce SoftValuedCache, which evicts more aggressively than SoftValuedHashMap. --- .../java/com/amazon/carbonado/filter/Filter.java | 17 +- .../carbonado/gen/DelegateStorableGenerator.java | 7 +- .../carbonado/gen/MasterStorableGenerator.java | 6 +- .../amazon/carbonado/gen/StorablePropertyMap.java | 8 +- .../java/com/amazon/carbonado/layout/Layout.java | 7 +- .../com/amazon/carbonado/layout/LayoutFactory.java | 8 +- .../amazon/carbonado/qe/JoinedQueryExecutor.java | 6 +- .../java/com/amazon/carbonado/qe/OrderingList.java | 8 +- .../amazon/carbonado/qe/PropertyFilterList.java | 8 +- .../amazon/carbonado/qe/QueryExecutorCache.java | 21 +- .../amazon/carbonado/qe/StandardQueryFactory.java | 6 +- .../amazon/carbonado/raw/GenericStorableCodec.java | 8 +- .../carbonado/repo/jdbc/JDBCStorableGenerator.java | 7 +- .../repo/jdbc/JDBCStorableIntrospector.java | 6 +- .../java/com/amazon/carbonado/repo/map/Key.java | 7 +- .../java/com/amazon/carbonado/spi/LobEngine.java | 7 +- .../com/amazon/carbonado/util/BelatedCreator.java | 5 +- .../java/com/amazon/carbonado/util/Converter.java | 4 +- .../carbonado/util/QuickConstructorGenerator.java | 7 +- .../com/amazon/carbonado/util/SoftValuedCache.java | 372 +++++++++++++++++++++ 20 files changed, 454 insertions(+), 71 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/util/SoftValuedCache.java diff --git a/src/main/java/com/amazon/carbonado/filter/Filter.java b/src/main/java/com/amazon/carbonado/filter/Filter.java index 6f3cc55..f20e30a 100644 --- a/src/main/java/com/amazon/carbonado/filter/Filter.java +++ b/src/main/java/com/amazon/carbonado/filter/Filter.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import org.cojen.util.SoftValuedHashMap; import org.cojen.util.WeakCanonicalSet; import org.cojen.util.WeakIdentityMap; @@ -36,6 +35,7 @@ import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.util.Appender; +import com.amazon.carbonado.util.SoftValuedCache; /** * An immutable tree structure representing a query result filter. Filters can @@ -88,7 +88,7 @@ public abstract class Filter implements Serializable, Append * @throws MalformedFilterException if filter expression is malformed */ public static Filter filterFor(Class type, String expression) { - Map> filterCache = getFilterCache(type); + SoftValuedCache> filterCache = getFilterCache(type); synchronized (filterCache) { Filter filter = filterCache.get(expression); if (filter == null) { @@ -108,7 +108,7 @@ public abstract class Filter implements Serializable, Append * @see OpenFilter */ public static OpenFilter getOpenFilter(Class type) { - Map> filterCache = getFilterCache(type); + SoftValuedCache> filterCache = getFilterCache(type); synchronized (filterCache) { Filter filter = filterCache.get(OPEN_KEY); if (filter == null) { @@ -128,7 +128,7 @@ public abstract class Filter implements Serializable, Append * @see ClosedFilter */ public static ClosedFilter getClosedFilter(Class type) { - Map> filterCache = getFilterCache(type); + SoftValuedCache> filterCache = getFilterCache(type); synchronized (filterCache) { Filter filter = filterCache.get(CLOSED_KEY); if (filter == null) { @@ -140,11 +140,14 @@ public abstract class Filter implements Serializable, Append } @SuppressWarnings("unchecked") - private static Map> getFilterCache(Class type) { + private static SoftValuedCache> + getFilterCache(Class type) + { synchronized (cCache) { - Map> filterCache = (Map>) cCache.get(type); + SoftValuedCache> filterCache = + (SoftValuedCache>) cCache.get(type); if (filterCache == null) { - filterCache = new SoftValuedHashMap(); + filterCache = SoftValuedCache.newCache(11); cCache.put(type, filterCache); } return filterCache; diff --git a/src/main/java/com/amazon/carbonado/gen/DelegateStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/DelegateStorableGenerator.java index 67e93f8..84b8c83 100644 --- a/src/main/java/com/amazon/carbonado/gen/DelegateStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/DelegateStorableGenerator.java @@ -29,11 +29,12 @@ import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.Storable; import com.amazon.carbonado.SupportException; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Generates and caches concrete implementations of {@link Storable} types * which delegate to {@link DelegateSupport}. The delegating classes extend @@ -43,10 +44,10 @@ import com.amazon.carbonado.SupportException; * @since 1.2 */ public class DelegateStorableGenerator { - private static final Map cCache; + private static final SoftValuedCache cCache; static { - cCache = new SoftValuedHashMap(); + cCache = SoftValuedCache.newCache(11); } /** diff --git a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java index 1c63d0c..ef73a2c 100644 --- a/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java +++ b/src/main/java/com/amazon/carbonado/gen/MasterStorableGenerator.java @@ -38,7 +38,6 @@ import org.cojen.classfile.Opcode; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.ConstraintException; import com.amazon.carbonado.IsolationLevel; @@ -55,6 +54,8 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.util.SoftValuedCache; + import static com.amazon.carbonado.gen.CommonMethodNames.*; /** @@ -88,7 +89,8 @@ public final class MasterStorableGenerator { private static final String DELETE_OP = "Delete"; // Cache of generated abstract classes. - private static Map> cCache = new SoftValuedHashMap(); + private static SoftValuedCache> cCache = + SoftValuedCache.newCache(11); /** * Returns an abstract implementation of the given Storable type, which diff --git a/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java b/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java index 6ad3466..0b8086b 100644 --- a/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java +++ b/src/main/java/com/amazon/carbonado/gen/StorablePropertyMap.java @@ -29,20 +29,20 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import org.cojen.util.SoftValuedHashMap; - import com.amazon.carbonado.Storable; import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Basic implementation for {@link Storable#propertyMap} method. * * @author Brian S O'Neill */ public class StorablePropertyMap extends AbstractMap { - private static final Map> cPropertyNamesForType = - new SoftValuedHashMap(); + private static final SoftValuedCache> cPropertyNamesForType = + SoftValuedCache.newCache(11); public static StorablePropertyMap createMap(Class type, S storable) { diff --git a/src/main/java/com/amazon/carbonado/layout/Layout.java b/src/main/java/com/amazon/carbonado/layout/Layout.java index 98df22c..6c28740 100644 --- a/src/main/java/com/amazon/carbonado/layout/Layout.java +++ b/src/main/java/com/amazon/carbonado/layout/Layout.java @@ -35,8 +35,6 @@ import org.joda.time.DateTime; import org.apache.commons.logging.LogFactory; -import org.cojen.util.SoftValuedHashMap; - import com.amazon.carbonado.CorruptEncodingException; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; @@ -53,6 +51,7 @@ import com.amazon.carbonado.synthetic.SyntheticKey; import com.amazon.carbonado.synthetic.SyntheticProperty; import com.amazon.carbonado.synthetic.SyntheticStorableBuilder; import com.amazon.carbonado.util.AnnotationDescPrinter; +import com.amazon.carbonado.util.SoftValuedCache; import com.amazon.carbonado.capability.ResyncCapability; @@ -63,10 +62,10 @@ import com.amazon.carbonado.capability.ResyncCapability; * @see LayoutFactory */ public class Layout { - private static Map> cReconstructed; + private static SoftValuedCache> cReconstructed; static { - cReconstructed = Collections.synchronizedMap(new SoftValuedHashMap()); + cReconstructed = SoftValuedCache.newCache(7); } static Class reconstruct(final Layout layout, ClassLoader loader) diff --git a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java index 7b7953b..836ee54 100644 --- a/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java +++ b/src/main/java/com/amazon/carbonado/layout/LayoutFactory.java @@ -29,8 +29,6 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; -import org.cojen.util.SoftValuedHashMap; - import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchDeadlockException; import com.amazon.carbonado.FetchException; @@ -55,6 +53,8 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.info.StorablePropertyAdapter; import com.amazon.carbonado.info.StorablePropertyAnnotation; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Factory for obtaining references to storable layouts. * @@ -69,7 +69,7 @@ public class LayoutFactory implements LayoutCapability { final Storage mLayoutStorage; final Storage mPropertyStorage; - private Map, Layout> mReconstructed; + private SoftValuedCache, Layout> mReconstructed; /** * @throws com.amazon.carbonado.SupportException if underlying repository @@ -339,7 +339,7 @@ public class LayoutFactory implements LayoutCapability { (Class reconstructed, Layout layout) { if (mReconstructed == null) { - mReconstructed = new SoftValuedHashMap(); + mReconstructed = SoftValuedCache.newCache(7); } mReconstructed.put(reconstructed, layout); } diff --git a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java index b370416..9a77f26 100644 --- a/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java +++ b/src/main/java/com/amazon/carbonado/qe/JoinedQueryExecutor.java @@ -32,7 +32,6 @@ import org.cojen.classfile.Modifiers; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; -import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; @@ -52,6 +51,7 @@ import com.amazon.carbonado.info.StorableIntrospector; import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.util.QuickConstructorGenerator; +import com.amazon.carbonado.util.SoftValuedCache; import com.amazon.carbonado.gen.CodeBuilderUtil; @@ -192,10 +192,10 @@ public class JoinedQueryExecutor private static final String INNER_LOOP_FV_FIELD_NAME = "innerLoopFilterValues"; private static final String ACTIVE_SOURCE_FIELD_NAME = "active"; - private static final Map cJoinerCursorClassCache; + private static final SoftValuedCache cJoinerCursorClassCache; static { - cJoinerCursorClassCache = new SoftValuedHashMap(); + cJoinerCursorClassCache = SoftValuedCache.newCache(11); } private static synchronized Joiner.Factory diff --git a/src/main/java/com/amazon/carbonado/qe/OrderingList.java b/src/main/java/com/amazon/carbonado/qe/OrderingList.java index 7feed40..95d8328 100644 --- a/src/main/java/com/amazon/carbonado/qe/OrderingList.java +++ b/src/main/java/com/amazon/carbonado/qe/OrderingList.java @@ -31,14 +31,14 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.cojen.util.SoftValuedHashMap; - import com.amazon.carbonado.Storable; import com.amazon.carbonado.info.ChainedProperty; import com.amazon.carbonado.info.OrderedProperty; import com.amazon.carbonado.info.StorableIntrospector; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Produces unmodifiable lists of {@link OrderedProperty orderings}. Instances * are immutable, canonical and cached. Calls to "equals" and "hashCode" are @@ -53,10 +53,10 @@ public class OrderingList extends AbstractList cCache; + private static final SoftValuedCache cCache; static { - cCache = new SoftValuedHashMap(); + cCache = SoftValuedCache.newCache(11); } /** diff --git a/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java b/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java index 7d7966b..cedc05c 100644 --- a/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java +++ b/src/main/java/com/amazon/carbonado/qe/PropertyFilterList.java @@ -26,8 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.cojen.util.SoftValuedHashMap; - import com.amazon.carbonado.Storable; import com.amazon.carbonado.filter.ExistsFilter; @@ -37,6 +35,8 @@ import com.amazon.carbonado.filter.PropertyFilter; import com.amazon.carbonado.filter.RelOp; import com.amazon.carbonado.filter.Visitor; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Produces unmodifable lists of PropertyFilters which were originally all * 'and'ed together. The filters are ordered such that all '=' operators are @@ -45,10 +45,10 @@ import com.amazon.carbonado.filter.Visitor; * @author Brian S O'Neill */ class PropertyFilterList extends AbstractList> { - private static Map, PropertyFilterList> cCache; + private static SoftValuedCache, PropertyFilterList> cCache; static { - cCache = new SoftValuedHashMap(); + cCache = SoftValuedCache.newCache(11); } /** diff --git a/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java index 806365e..e114943 100644 --- a/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java +++ b/src/main/java/com/amazon/carbonado/qe/QueryExecutorCache.java @@ -20,7 +20,6 @@ package com.amazon.carbonado.qe; import java.util.Map; -import org.cojen.util.SoftValuedHashMap; import org.cojen.util.WeakIdentityMap; import com.amazon.carbonado.RepositoryException; @@ -28,6 +27,8 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.filter.Filter; +import com.amazon.carbonado.util.SoftValuedCache; + /** * QueryExecutors should be cached since expensive analysis is often required * to build them. @@ -38,7 +39,7 @@ public class QueryExecutorCache implements QueryExecutorFact private final QueryExecutorFactory mFactory; // Maps filters to maps which map ordering lists (possibly with hints) to executors. - private final Map, Map>> mFilterToExecutor; + private final Map, SoftValuedCache>> mFilterToExecutor; public QueryExecutorCache(QueryExecutorFactory factory) { if (factory == null) { @@ -62,12 +63,12 @@ public class QueryExecutorCache implements QueryExecutorFact public QueryExecutor executor(Filter filter, OrderingList ordering, QueryHints hints) throws RepositoryException { - Map> map; + SoftValuedCache> cache; synchronized (mFilterToExecutor) { - map = mFilterToExecutor.get(filter); - if (map == null) { - map = new SoftValuedHashMap(7); - mFilterToExecutor.put(filter, map); + cache = mFilterToExecutor.get(filter); + if (cache == null) { + cache = SoftValuedCache.newCache(7); + mFilterToExecutor.put(filter, cache); } } @@ -79,11 +80,11 @@ public class QueryExecutorCache implements QueryExecutorFact } QueryExecutor executor; - synchronized (map) { - executor = map.get(key); + synchronized (cache) { + executor = cache.get(key); if (executor == null) { executor = mFactory.executor(filter, ordering, hints); - map.put(key, executor); + cache.put(key, executor); } } diff --git a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java index 4c80075..d6b99d6 100644 --- a/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java +++ b/src/main/java/com/amazon/carbonado/qe/StandardQueryFactory.java @@ -31,6 +31,8 @@ import com.amazon.carbonado.Storable; import com.amazon.carbonado.filter.Filter; import com.amazon.carbonado.filter.FilterValues; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Builds and caches StandardQuery instances. * @@ -40,7 +42,7 @@ public abstract class StandardQueryFactory implements QueryF private final Class mType; private final boolean mLazySetExecutor; - private final Map> mStringToQuery; + private final SoftValuedCache> mStringToQuery; // Maps filters to maps which map ordering lists to queries. private final Map, Map, Query>> mFilterToQuery; @@ -59,7 +61,7 @@ public abstract class StandardQueryFactory implements QueryF } mType = type; mLazySetExecutor = lazySetExecutor; - mStringToQuery = new SoftValuedHashMap(7); + mStringToQuery = SoftValuedCache.newCache(7); mFilterToQuery = new WeakIdentityMap(7); } diff --git a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java index ed2d09d..22f36ea 100644 --- a/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java +++ b/src/main/java/com/amazon/carbonado/raw/GenericStorableCodec.java @@ -32,7 +32,6 @@ import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; import org.cojen.util.IntHashMap; import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; import org.cojen.util.ThrowUnchecked; import com.amazon.carbonado.CorruptEncodingException; @@ -55,6 +54,7 @@ import com.amazon.carbonado.gen.StorableGenerator; import com.amazon.carbonado.gen.TriggerSupport; import com.amazon.carbonado.util.QuickConstructorGenerator; +import com.amazon.carbonado.util.SoftValuedCache; /** * Generic codec that supports any kind of storable by auto-generating and @@ -67,7 +67,7 @@ public class GenericStorableCodec implements StorableCodec implements StorableCodec { private static final int INITIAL_VERSION = 2; private static final int INCREMENT_VERSION = 3; - private static final Map> cCache; + private static final SoftValuedCache> cCache; static { - cCache = new SoftValuedHashMap(); + cCache = SoftValuedCache.newCache(11); } static Class getGeneratedClass(JDBCStorableInfo info, diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java index 042974f..acba43e 100644 --- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java +++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorableIntrospector.java @@ -46,7 +46,6 @@ import org.apache.commons.logging.LogFactory; import org.cojen.classfile.TypeDesc; import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; import org.cojen.util.ThrowUnchecked; import com.amazon.carbonado.capability.IndexInfo; @@ -66,6 +65,8 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.info.StorablePropertyAdapter; import com.amazon.carbonado.info.StorablePropertyConstraint; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Provides additional metadata for a {@link Storable} type needed by * JDBCRepository. The storable type must match to a table in an external @@ -80,7 +81,8 @@ import com.amazon.carbonado.info.StorablePropertyConstraint; public class JDBCStorableIntrospector extends StorableIntrospector { // Maps compound keys to softly referenced JDBCStorableInfo objects. @SuppressWarnings("unchecked") - private static Map> cCache = new SoftValuedHashMap(); + private static SoftValuedCache> cCache = + SoftValuedCache.newCache(11); /** * Examines the given class and returns a JDBCStorableInfo describing it. A diff --git a/src/main/java/com/amazon/carbonado/repo/map/Key.java b/src/main/java/com/amazon/carbonado/repo/map/Key.java index 9348516..86a0023 100644 --- a/src/main/java/com/amazon/carbonado/repo/map/Key.java +++ b/src/main/java/com/amazon/carbonado/repo/map/Key.java @@ -34,7 +34,6 @@ import org.cojen.classfile.Modifiers; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; -import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.Storable; @@ -45,6 +44,8 @@ import com.amazon.carbonado.info.StorableProperty; import com.amazon.carbonado.gen.CodeBuilderUtil; +import com.amazon.carbonado.util.SoftValuedCache; + /** * * @@ -82,10 +83,10 @@ class Key implements Comparable> { void setKeyValues(S storable, Object[] identityValues, Object rangeValue); } - private static final Map mAssigners; + private static final SoftValuedCache mAssigners; static { - mAssigners = new SoftValuedHashMap(); + mAssigners = SoftValuedCache.newCache(11); } public static synchronized Assigner getAssigner(Class clazz) { diff --git a/src/main/java/com/amazon/carbonado/spi/LobEngine.java b/src/main/java/com/amazon/carbonado/spi/LobEngine.java index 143bd26..0262890 100644 --- a/src/main/java/com/amazon/carbonado/spi/LobEngine.java +++ b/src/main/java/com/amazon/carbonado/spi/LobEngine.java @@ -30,7 +30,6 @@ import java.util.Map; import java.util.List; import org.cojen.util.KeyFactory; -import org.cojen.util.SoftValuedHashMap; import com.amazon.carbonado.Cursor; import com.amazon.carbonado.FetchException; @@ -58,6 +57,8 @@ import com.amazon.carbonado.lob.Lob; import com.amazon.carbonado.sequence.SequenceValueGenerator; import com.amazon.carbonado.sequence.SequenceValueProducer; +import com.amazon.carbonado.util.SoftValuedCache; + /** * Complete Lob support for repositories, although repository is responsible * for binding Lob properties to this engine. Lobs are referenced by locators, @@ -88,7 +89,7 @@ public class LobEngine { final Storage mLobBlockStorage; final SequenceValueProducer mLocatorSequence; - private Map mTriggers; + private SoftValuedCache mTriggers; /** * @param lobRepo storage for Lobs - should not be replicated @@ -460,7 +461,7 @@ public class LobEngine { } if (mTriggers == null) { - mTriggers = new SoftValuedHashMap(); + mTriggers = SoftValuedCache.newCache(7); } mTriggers.put(key, trigger); diff --git a/src/main/java/com/amazon/carbonado/util/BelatedCreator.java b/src/main/java/com/amazon/carbonado/util/BelatedCreator.java index 513a0c0..cc476cf 100644 --- a/src/main/java/com/amazon/carbonado/util/BelatedCreator.java +++ b/src/main/java/com/amazon/carbonado/util/BelatedCreator.java @@ -36,7 +36,6 @@ import org.cojen.classfile.Modifiers; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; -import org.cojen.util.SoftValuedHashMap; /** * Generic one-shot factory which supports late object creation. If the object @@ -54,12 +53,12 @@ import org.cojen.util.SoftValuedHashMap; public abstract class BelatedCreator { private static final String REF_FIELD_NAME = "ref"; - private static final Map, Class> cWrapperCache; + private static final SoftValuedCache, Class> cWrapperCache; private static final ExecutorService cThreadPool; static { - cWrapperCache = new SoftValuedHashMap(); + cWrapperCache = SoftValuedCache.newCache(11); cThreadPool = Executors.newCachedThreadPool(new TFactory()); } diff --git a/src/main/java/com/amazon/carbonado/util/Converter.java b/src/main/java/com/amazon/carbonado/util/Converter.java index 61f832a..44f28eb 100644 --- a/src/main/java/com/amazon/carbonado/util/Converter.java +++ b/src/main/java/com/amazon/carbonado/util/Converter.java @@ -38,7 +38,6 @@ import org.cojen.classfile.Opcode; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; -import org.cojen.util.SoftValuedHashMap; /** * General purpose type converter. Custom conversions are possible by supplying @@ -50,7 +49,8 @@ import org.cojen.util.SoftValuedHashMap; * @since 1.2 */ public abstract class Converter { - private static final Map> cCache = new SoftValuedHashMap(); + private static final SoftValuedCache> cCache = + SoftValuedCache.newCache(7); /** * @param converterType type of converter to generate diff --git a/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java b/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java index 7a10440..a746c1f 100644 --- a/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java +++ b/src/main/java/com/amazon/carbonado/util/QuickConstructorGenerator.java @@ -29,7 +29,6 @@ import org.cojen.classfile.CodeBuilder; import org.cojen.classfile.TypeDesc; import org.cojen.util.ClassInjector; import org.cojen.util.WeakIdentityMap; -import org.cojen.util.SoftValuedHashMap; /** * Generates code to invoke constructors. This is a replacement for {@link @@ -48,7 +47,7 @@ import org.cojen.util.SoftValuedHashMap; public class QuickConstructorGenerator { // Map> @SuppressWarnings("unchecked") - private static Map, Map, Object>> cCache = new WeakIdentityMap(); + private static Map, SoftValuedCache, Object>> cCache = new WeakIdentityMap(); /** * Returns a factory instance for one type of object. Each method in the @@ -87,9 +86,9 @@ public class QuickConstructorGenerator { */ @SuppressWarnings("unchecked") public static synchronized F getInstance(Class objectType, Class factory) { - Map, Object> innerCache = cCache.get(factory); + SoftValuedCache, Object> innerCache = cCache.get(factory); if (innerCache == null) { - innerCache = new SoftValuedHashMap(); + innerCache = SoftValuedCache.newCache(7); cCache.put(factory, innerCache); } F instance = (F) innerCache.get(objectType); diff --git a/src/main/java/com/amazon/carbonado/util/SoftValuedCache.java b/src/main/java/com/amazon/carbonado/util/SoftValuedCache.java new file mode 100644 index 0000000..3e9e767 --- /dev/null +++ b/src/main/java/com/amazon/carbonado/util/SoftValuedCache.java @@ -0,0 +1,372 @@ +/* + * Copyright 2010 Amazon Technologies, Inc. or its affiliates. + * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks + * of Amazon Technologies, Inc. or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazon.carbonado.util; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + +/** + * Simple thread-safe cache which evicts entries via a shared background + * thread. Cache permits null keys, but not null values. + * + * @author Brian S O'Neill + */ +public abstract class SoftValuedCache { + // Design note: Public class is abstract to make it easy to swap out this + // implementation with something available in a shared collection library. + + public static SoftValuedCache newCache(int capacity) { + return new Impl(capacity); + } + + public abstract int size(); + + public abstract boolean isEmpty(); + + public abstract V get(K key); + + public abstract V put(K key, V value); + + public abstract V putIfAbsent(K key, V value); + + public abstract V remove(K key); + + public abstract boolean remove(K key, V value); + + public abstract boolean replace(K key, V oldValue, V newValue); + + public abstract V replace(K key, V value); + + public abstract void clear(); + + public abstract String toString(); + + private static class Impl extends SoftValuedCache { + private static final float LOAD_FACTOR = 0.75f; + + private Entry[] mEntries; + private int mSize; + private int mThreshold; + + Impl(int capacity) { + mEntries = new Entry[capacity]; + mThreshold = (int) (capacity * LOAD_FACTOR); + } + + @Override + public synchronized int size() { + return mSize; + } + + @Override + public synchronized boolean isEmpty() { + return mSize == 0; + } + + @Override + public synchronized V get(K key) { + int hash = key == null ? 0 : key.hashCode(); + Entry[] entries = mEntries; + int index = (hash & 0x7fffffff) % entries.length; + for (Entry e = entries[index]; e != null; e = e.mNext) { + if (e.matches(key, hash)) { + return e.get(); + } + } + return null; + } + + @Override + public synchronized V put(K key, V value) { + int hash = key == null ? 0 : key.hashCode(); + Entry[] entries = mEntries; + int index = (hash & 0x7fffffff) % entries.length; + for (Entry e = entries[index], prev = null; e != null; e = e.mNext) { + if (e.matches(key, hash)) { + V old = e.get(); + e.clear(); + Entry newEntry; + if (prev == null) { + newEntry = new Entry(this, hash, key, value, e.mNext); + } else { + prev.mNext = e.mNext; + newEntry = new Entry(this, hash, key, value, entries[index]); + } + entries[index] = newEntry; + return old; + } else { + prev = e; + } + } + + if (mSize >= mThreshold) { + cleanup(); + if (mSize >= mThreshold) { + rehash(); + entries = mEntries; + index = (hash & 0x7fffffff) % entries.length; + } + } + + entries[index] = new Entry(this, hash, key, value, entries[index]); + mSize++; + return null; + } + + @Override + public synchronized V putIfAbsent(K key, V value) { + V existing = get(key); + return existing == null ? put(key, value) : existing; + } + + @Override + public synchronized V remove(K key) { + int hash = key == null ? 0 : key.hashCode(); + Entry[] entries = mEntries; + int index = (hash & 0x7fffffff) % entries.length; + for (Entry e = entries[index], prev = null; e != null; e = e.mNext) { + if (e.matches(key, hash)) { + V old = e.get(); + e.clear(); + if (prev == null) { + entries[index] = e.mNext; + } else { + prev.mNext = e.mNext; + } + mSize--; + return old; + } else { + prev = e; + } + } + return null; + } + + @Override + public synchronized boolean remove(K key, V value) { + V existing = get(key); + if (existing != null && existing.equals(value)) { + remove(key); + return true; + } else { + return false; + } + } + + @Override + public synchronized boolean replace(K key, V oldValue, V newValue) { + V existing = get(key); + if (existing != null && existing.equals(oldValue)) { + put(key, newValue); + return true; + } else { + return false; + } + } + + @Override + public synchronized V replace(K key, V value) { + return get(key) == null ? null : put(key, value); + } + + @Override + public synchronized void clear() { + Entry[] entries = mEntries; + for (int i=entries.length; --i>=0; ) { + Entry e = entries[i]; + if (e != null) { + e.clear(); + entries[i] = null; + } + } + mSize = 0; + } + + @Override + public synchronized String toString() { + if (isEmpty()) { + return "{}"; + } + + StringBuilder b = new StringBuilder(); + b.append('{'); + + Entry[] entries = mEntries; + int removed = 0; + boolean any = false; + + for (int i=entries.length; --i>=0 ;) { + for (Entry e = entries[i], prev = null; e != null; e = e.mNext) { + V value = e.get(); + if (value == null) { + // Clean up after a cleared Reference. + if (prev == null) { + entries[i] = e.mNext; + } else { + prev.mNext = e.mNext; + } + removed++; + } else { + prev = e; + if (any) { + b.append(',').append(' '); + } + K key = e.mKey; + b.append(key).append('=').append(value); + any = true; + } + } + } + + mSize -= removed; + + b.append('}'); + return b.toString(); + } + + synchronized void removeCleared(Entry cleared) { + Entry[] entries = mEntries; + int index = (cleared.mHash & 0x7fffffff) % entries.length; + for (Entry e = entries[index], prev = null; e != null; e = e.mNext) { + if (e == cleared) { + if (prev == null) { + entries[index] = e.mNext; + } else { + prev.mNext = e.mNext; + } + mSize--; + return; + } else { + prev = e; + } + } + } + + private synchronized void cleanup() { + Entry[] entries = mEntries; + int removed = 0; + + for (int i=entries.length; --i>=0 ;) { + for (Entry e = entries[i], prev = null; e != null; e = e.mNext) { + if (e.get() == null) { + // Clean up after a cleared Reference. + if (prev == null) { + entries[i] = e.mNext; + } else { + prev.mNext = e.mNext; + } + removed++; + } else { + prev = e; + } + } + } + + mSize -= removed; + } + + private synchronized void rehash() { + Entry[] oldEntries = mEntries; + int newCapacity = oldEntries.length * 2 + 1; + Entry[] newEntries = new Entry[newCapacity]; + int removed = 0; + + for (int i=oldEntries.length; --i>=0 ;) { + for (Entry old = oldEntries[i]; old != null; ) { + Entry e = old; + old = old.mNext; + // Only copy entry if its value hasn't been cleared. + if (e.get() == null) { + removed++; + } else { + int index = (e.mHash & 0x7fffffff) % newCapacity; + e.mNext = newEntries[index]; + newEntries[index] = e; + } + } + } + + mEntries = newEntries; + mSize -= removed; + mThreshold = (int) (newCapacity * LOAD_FACTOR); + } + } + + private static class Entry extends Ref { + final Impl mCache; + final int mHash; + final K mKey; + Entry mNext; + + Entry(Impl cache, int hash, K key, V value, Entry next) { + super(value); + mCache = cache; + mHash = hash; + mKey = key; + mNext = next; + } + + @Override + void remove() { + mCache.removeCleared(this); + } + + boolean matches(K key, int hash) { + return hash == mHash && (key == null ? mKey == null : key.equals(mKey)); + } + } + + final static Evictor cEvictor; + + static { + Evictor evictor = new Evictor(); + evictor.setName("SoftValuedCache Evictor"); + evictor.setDaemon(true); + evictor.setPriority(Thread.MAX_PRIORITY); + evictor.start(); + cEvictor = evictor; + } + + private static abstract class Ref extends SoftReference { + Ref(T referent) { + super(referent, cEvictor.mQueue); + } + + abstract void remove(); + } + + private static class Evictor extends Thread { + final ReferenceQueue mQueue; + + Evictor() { + mQueue = new ReferenceQueue(); + } + + @Override + public void run() { + try { + while (true) { + ((Ref) mQueue.remove()).remove(); + } + } catch (InterruptedException e) { + } + } + } +} -- cgit v1.2.3