All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.hasor.cobble.dynamic.JdkWeakCache Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package net.hasor.cobble.dynamic;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * copy form jdk 1.8 java.lang.reflect.WeakCache
 */
final class JdkWeakCache {

    private final ReferenceQueue                                         refQueue   = new ReferenceQueue<>();
    // the key type is Object for supporting null key
    private final ConcurrentMap>> map        = new ConcurrentHashMap<>();
    private final ConcurrentMap, Boolean>                       reverseMap = new ConcurrentHashMap<>();
    private final BiFunction                                       subKeyFactory;
    private final BiFunction                                       valueFactory;

    /**
     * Construct an instance of {@code WeakCache}
     *
     * @param subKeyFactory a function mapping a pair of {@code (key, parameter) -> sub-key}
     * @param valueFactory  a function mapping a pair of {@code (key, parameter) -> value}
     * @throws NullPointerException if {@code subKeyFactory} or {@code valueFactory} is null.
     */
    public JdkWeakCache(BiFunction subKeyFactory, BiFunction valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

    /**
     * Look-up the value through the cache. This always evaluates the
     * {@code subKeyFactory} function and optionally evaluates
     * {@code valueFactory} function if there is no entry in the cache for given
     * pair of (key, subKey) or the entry has already been cleared.
     *
     * @param key       possibly null key
     * @param parameter parameter used together with key to create sub-key and value (should not be null)
     * @return the cached value (never null)
     * @throws NullPointerException if {@code parameter} passed in or {@code sub-key} calculated by
     *                              {@code subKeyFactory} or {@code value} calculated by {@code valueFactory} is null.
     */
    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();

        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

    /**
     * Checks whether the specified non-null value is already present in this {@code WeakCache}.
     * The check is made using identity comparison regardless of whether value's class overrides {@link Object#equals} or not.
     *
     * @param value the non-null value to check
     * @return true if given {@code value} is already cached
     * @throws NullPointerException if value is null
     */
    public boolean containsValue(V value) {
        Objects.requireNonNull(value);

        expungeStaleEntries();
        return reverseMap.containsKey(new LookupValue<>(value));
    }

    /** Returns the current number of cached entries that can decrease over time when keys/values are GC-ed. */
    public int size() {
        expungeStaleEntries();
        return reverseMap.size();
    }

    private void expungeStaleEntries() {
        CacheKey cacheKey;
        while ((cacheKey = (CacheKey) refQueue.poll()) != null) {
            cacheKey.expungeFrom(map, reverseMap);
        }
    }

    /** A factory {@link Supplier} that implements the lazy synchronized construction of the value and installment of it into the cache. */
    private final class Factory implements Supplier {

        private final K                                  key;
        private final P                                  parameter;
        private final Object                             subKey;
        private final ConcurrentMap> valuesMap;

        Factory(K key, P parameter, Object subKey, ConcurrentMap> valuesMap) {
            this.key = key;
            this.parameter = parameter;
            this.subKey = subKey;
            this.valuesMap = valuesMap;
        }

        @Override
        public synchronized V get() { // serialize access
            // re-check
            Supplier supplier = valuesMap.get(subKey);
            if (supplier != this) {
                // something changed while we were waiting:
                // might be that we were replaced by a CacheValue
                // or were removed because of failure ->
                // return null to signal WeakCache.get() to retry
                // the loop
                return null;
            }
            // else still us (supplier == this)

            // create new value
            V value = null;
            try {
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            // the only path to reach here is with non-null value
            assert value != null;

            // wrap value with CacheValue (WeakReference)
            CacheValue cacheValue = new CacheValue<>(value);

            // put into reverseMap
            reverseMap.put(cacheValue, Boolean.TRUE);

            // try replacing us with CacheValue (this should always succeed)
            if (!valuesMap.replace(subKey, this, cacheValue)) {
                throw new AssertionError("Should not reach here");
            }

            // successfully replaced us with new CacheValue -> return the value
            // wrapped by it
            return value;
        }
    }

    /**
     * Common type of value suppliers that are holding a referent.
     * The {@link #equals} and {@link #hashCode} of implementations is defined to compare the referent by identity.
     */
    private interface Value extends Supplier {
    }

    /**
     * An optimized {@link Value} used to look-up the value in {@link JdkWeakCache#containsValue} method so that we are not
     * constructing the whole {@link CacheValue} just to look-up the referent.
     */
    private static final class LookupValue implements Value {
        private final V value;

        LookupValue(V value) {
            this.value = value;
        }

        @Override
        public V get() {
            return value;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(value); // compare by identity
        }

        @Override
        public boolean equals(Object obj) {
            return obj == this || obj instanceof Value && this.value == ((Value) obj).get();  // compare by identity
        }
    }

    /** A {@link Value} that weakly references the referent. */
    private static final class CacheValue extends WeakReference implements Value {
        private final int hash;

        CacheValue(V value) {
            super(value);
            this.hash = System.identityHashCode(value); // compare by identity
        }

        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            V value;
            return obj == this || obj instanceof Value &&
                    // cleared CacheValue is only equal to itself
                    (value = get()) != null && value == ((Value) obj).get(); // compare by identity
        }
    }

    /**
     * CacheKey containing a weakly referenced {@code key}. It registers
     * itself with the {@code refQueue} so that it can be used to expunge
     * the entry when the {@link WeakReference} is cleared.
     */
    private static final class CacheKey extends WeakReference {

        // a replacement for null keys
        private static final Object NULL_KEY = new Object();

        static  Object valueOf(K key, ReferenceQueue refQueue) {
            return key == null
                    // null key means we can't weakly reference it,
                    // so we use a NULL_KEY singleton as cache key
                    ? NULL_KEY
                    // non-null key requires wrapping with a WeakReference
                    : new CacheKey<>(key, refQueue);
        }

        private final int hash;

        private CacheKey(K key, ReferenceQueue refQueue) {
            super(key, refQueue);
            this.hash = System.identityHashCode(key);  // compare by identity
        }

        @Override
        public int hashCode() {
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            K key;
            return obj == this || obj != null && obj.getClass() == this.getClass() &&
                    // cleared CacheKey is only equal to itself
                    (key = this.get()) != null &&
                    // compare key by identity
                    key == ((CacheKey) obj).get();
        }

        void expungeFrom(ConcurrentMap> map, ConcurrentMap reverseMap) {
            // removing just by key is always safe here because after a CacheKey
            // is cleared and enqueue-ed it is only equal to itself
            // (see equals method)...
            ConcurrentMap valuesMap = map.remove(this);
            // remove also from reverseMap if needed
            if (valuesMap != null) {
                for (Object cacheValue : valuesMap.values()) {
                    reverseMap.remove(cacheValue);
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy