rickbw.incubator.cache.MultiCache.java Source code

Java tutorial

Introduction

Here is the source code for rickbw.incubator.cache.MultiCache.java

Source

/* Copyright 2014 Rick Warren
 *
 * 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 rickbw.incubator.cache;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.base.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheStats;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.util.concurrent.UncheckedExecutionException;

/**
 * A {@link Cache} that partitions entries into one of multiple delegate
 * {@link Cache}s on insertion, so that different subsets of the key space
 * can have different policies attached to them.
 *
 * Delegate caches are identified by means of a "cache key", which is derived
 * from an element key by means of a provided function. The cache key may be
 * any object, so long as it provides well-behaved
 * {@link Object#equals(Object) equals} and {@link Object#hashCode() hashCode}
 * implementations.
 */
public class MultiCache<K, V> implements Cache<K, V> {

    private final CacheMap asMap = new CacheMap();

    private final Class<K> elementKeyClass;
    private final ImmutableMap<Object, Cache<K, V>> delegates;
    private final Function<? super K, ?> cacheKeyCalc;

    /**
     * @param caches    Delegate caches, identified by their cache keys.
     * @param cacheKeyCalc  A {@link Function} to determine the cache key for
     *          a given element key. This function must be very fast, must
     *          return the same answer for a given element key every time,
     *          must return a key for which a {@link Cache} exists in the
     *          given {@link Map}, and must never return null.
     */
    public MultiCache(final Class<K> elementKeyClass, final Map<?, ? extends Cache<K, V>> caches,
            final Function<? super K, ?> cacheKeyCalc) {
        this.elementKeyClass = Objects.requireNonNull(elementKeyClass);
        this.delegates = ImmutableMap.copyOf(caches);
        this.cacheKeyCalc = Objects.requireNonNull(cacheKeyCalc);
    }

    @Override
    public final @Nullable V getIfPresent(@Nullable final Object elementKey) {
        final Cache<K, V> cache = getCache(elementKey);
        if (cache != null) {
            return cache.getIfPresent(elementKey);
        } else {
            return null;
        }
    }

    @Override
    public final @Nonnull V get(final K elementKey, final Callable<? extends V> valueLoader)
            throws ExecutionException {
        final Cache<K, V> cache = getCache(elementKey);
        if (cache != null) {
            return cache.get(elementKey, valueLoader);
        } else {
            throw new UncheckedExecutionException(noCacheForKey(elementKey));
        }
    }

    @Override
    public final ImmutableMap<K, V> getAllPresent(final Iterable<?> elementKeys) {
        final ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
        for (final Object elementKey : elementKeys) {
            final Cache<K, V> cache = getCache(elementKey);
            if (cache != null) {
                final V value = cache.getIfPresent(elementKey);
                if (value != null) {
                    builder.put(this.elementKeyClass.cast(elementKey), value);
                }
            }
        }
        return builder.build();
    }

    @Override
    public final void put(final K elementKey, final V value) {
        final Cache<K, V> cache = getCacheNonNull(elementKey);
        cache.put(elementKey, value);
    }

    @Override
    public final void putAll(final Map<? extends K, ? extends V> entries) {
        for (final Map.Entry<? extends K, ? extends V> entry : entries.entrySet()) {
            final K elementKey = entry.getKey();
            final Cache<K, V> cache = getCacheNonNull(elementKey);
            cache.put(elementKey, entry.getValue());
        }
    }

    @Override
    public final long size() {
        long totalSize = 0L;
        for (final Cache<?, ?> delegate : this.delegates.values()) {
            totalSize += delegate.size();
        }
        return totalSize;
    }

    @Override
    public final ConcurrentMap<K, V> asMap() {
        return this.asMap;
    }

    @Override
    public final void invalidate(final Object elementKey) {
        final Cache<K, V> cache = getCache(elementKey);
        if (cache != null) {
            cache.invalidate(elementKey);
        }
    }

    @Override
    public final void invalidateAll(final Iterable<?> elementKeys) {
        for (final Object elementKey : elementKeys) {
            final Cache<K, V> cache = getCache(elementKey);
            if (cache != null) {
                cache.invalidate(elementKey);
            }
        }
    }

    @Override
    public final void invalidateAll() {
        for (final Cache<?, ?> delegate : this.delegates.values()) {
            delegate.invalidateAll();
        }
    }

    @Override
    public final void cleanUp() {
        for (final Cache<?, ?> delegate : this.delegates.values()) {
            delegate.cleanUp();
        }
    }

    @Override
    public final CacheStats stats() {
        long hitCount = 0L;
        long missCount = 0L;
        long loadSuccessCount = 0L;
        long loadExceptionCount = 0L;
        long totalLoadTime = 0L;
        long evictionCount = 0L;

        for (final Cache<?, ?> delegate : this.delegates.values()) {
            final CacheStats stats = delegate.stats();
            hitCount += stats.hitCount();
            missCount += stats.missCount();
            loadSuccessCount += stats.loadSuccessCount();
            loadExceptionCount += stats.loadExceptionCount();
            totalLoadTime += stats.totalLoadTime();
            evictionCount += stats.evictionCount();
        }

        return new CacheStats(hitCount, missCount, loadSuccessCount, loadExceptionCount, totalLoadTime,
                evictionCount);
    }

    protected @Nullable Cache<K, V> getCache(final Object elementKey) {
        @Nullable
        final Object cacheKey = cacheKey(elementKey);
        @Nullable
        final Cache<K, V> cache = this.delegates.get(cacheKey);
        return cache;
    }

    protected @Nonnull Cache<K, V> getCacheNonNull(final Object elementKey) {
        @Nullable
        final Cache<K, V> cache = getCache(elementKey);
        if (cache != null) {
            return cache;
        } else {
            throw noCacheForKey(elementKey);
        }
    }

    private @Nullable Object cacheKey(@Nullable final Object key) {
        if (this.elementKeyClass.isInstance(key)) {
            return this.cacheKeyCalc.apply(this.elementKeyClass.cast(key));
        } else {
            return null;
        }
    }

    private static IllegalArgumentException noCacheForKey(final Object elementKey) {
        return new IllegalArgumentException("Unable to identify cache for element key " + elementKey);
    }

    private final class CacheMap extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
        private final Set<Map.Entry<K, V>> entrySet = new CacheSet();

        @Override
        public V get(final Object elementKey) {
            final Cache<K, V> cache = getCache(elementKey);
            if (cache != null) {
                return cache.asMap().get(elementKey);
            } else {
                return null;
            }
        }

        @Override
        public boolean containsKey(final Object elementKey) {
            final Cache<K, V> cache = getCache(elementKey);
            if (cache != null) {
                return cache.asMap().containsKey(elementKey);
            } else {
                return false;
            }
        }

        @Override
        public V put(final K elementKey, final V value) {
            final Cache<K, V> cache = getCacheNonNull(elementKey);
            return cache.asMap().put(elementKey, value);
        }

        @Override
        public V putIfAbsent(final K elementKey, final V value) {
            final Cache<K, V> cache = getCacheNonNull(elementKey);
            return cache.asMap().putIfAbsent(elementKey, value);
        }

        @Override
        public boolean remove(final Object elementKey, final Object value) {
            final Cache<K, V> cache = getCache(elementKey);
            if (cache != null) {
                return cache.asMap().remove(elementKey, value);
            } else {
                return false;
            }
        }

        @Override
        public boolean replace(final K elementKey, final V oldValue, final V newValue) {
            final Cache<K, V> cache = getCacheNonNull(elementKey);
            return cache.asMap().replace(elementKey, oldValue, newValue);
        }

        @Override
        public V replace(final K elementKey, final V value) {
            final Cache<K, V> cache = getCacheNonNull(elementKey);
            return cache.asMap().replace(elementKey, value);
        }

        @Override
        public Set<Map.Entry<K, V>> entrySet() {
            return this.entrySet;
        }
    }

    private final class CacheSet extends AbstractSet<Map.Entry<K, V>> {
        @Override
        public Iterator<Map.Entry<K, V>> iterator() {
            final Collection<Iterator<Map.Entry<K, V>>> iterators = new ArrayList<>(
                    MultiCache.this.delegates.size());
            for (final Cache<K, V> delegate : MultiCache.this.delegates.values()) {
                iterators.add(delegate.asMap().entrySet().iterator());
            }
            return Iterators.concat(iterators.iterator());
        }

        @Override
        public boolean add(final Map.Entry<K, V> entry) {
            final K elementKey = entry.getKey();
            final Cache<K, V> cache = getCacheNonNull(elementKey);
            return cache.asMap().entrySet().add(entry);
        }

        @Override
        public int size() {
            final long totalSize = MultiCache.this.size();
            if (totalSize <= Integer.MAX_VALUE) {
                return (int) totalSize;
            } else {
                return Integer.MAX_VALUE;
            }
        }
    }

}