/*
* Copyright (C) 2012 Facebook, Inc.
*
* 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.facebook.concurrency;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.base.Preconditions;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import com.facebook.util.exceptions.ExceptionHandler;
public class CaffeineConcurrentCache<K, V, E extends Exception> implements ConcurrentCache<K, V, E> {
private final LoadingCache<K, CallableSnapshot<V, E>> cache;
private final ExceptionHandler<E> exceptionHandler;
private final ConcurrentMap<K, CallableSnapshot<V, E>> cacheAsMap;
/**
* callers may pass in a Cache object configured appropriately
*
* @param valueFactory
* @param exceptionHandler
* @param cacheBuilder - result of Caffeine.newBuilder() + any config (caller may customize the cache)
*/
public CaffeineConcurrentCache(
ValueFactory<K, V, E> valueFactory,
ExceptionHandler<E> exceptionHandler,
Caffeine<Object, Object> cacheBuilder
) {
this.exceptionHandler = exceptionHandler;
this.cache = cacheBuilder.build(new CacheValueLoader<>(valueFactory, exceptionHandler));
cacheAsMap = cache.asMap();
}
public CaffeineConcurrentCache(
ValueFactory<K, V, E> valueFactory, ExceptionHandler<E> exceptionHandler
) {
this(valueFactory, exceptionHandler, Caffeine.newBuilder());
}
@Override
public V get(final K key) throws E {
return cache.get(key).get();
}
@Override
public V put(K key, V value) throws E {
CallableSnapshot<V, E> putResult = cacheAsMap.put(key, new CallableSnapshot<>(() -> value, exceptionHandler));
return putResult == null ? null : putResult.get();
}
@Override
public V remove(K key) throws E {
CallableSnapshot<V, E> removeResult = cacheAsMap.remove(key);
return removeResult == null ? null : removeResult.get();
}
@Override
public boolean removeIfError(K key) {
CallableSnapshot<V, E> snapshot = cache.getIfPresent(key);
if (snapshot != null && snapshot.getException() != null) {
cacheAsMap.remove(key, snapshot);
return true;
}
return false;
}
@Override
public void clear() {
cache.invalidateAll();
}
@Override
public void prune() {
cache.cleanUp();
}
@Override
public int size() {
long sizeInLong = cache.estimatedSize();
Preconditions.checkState(sizeInLong < Integer.MAX_VALUE, "overflow on cache size");
return (int) sizeInLong;
}
@Override
public Iterator<Map.Entry<K, CallableSnapshot<V, E>>> iterator() {
Iterator<Map.Entry<K, CallableSnapshot<V, E>>> iterator = cacheAsMap.entrySet().iterator();
return iterator;
}
@Override
public CallableSnapshot<V, E> getIfPresent(K key) {
return cache.getIfPresent(key);
}
private static class CacheValueLoader<K, V, E extends Exception> implements CacheLoader<K, CallableSnapshot<V, E>> {
private final ValueFactory<K, V, E> valueFactory;
private final ExceptionHandler<E> exceptionHandler;
private CacheValueLoader(ValueFactory<K, V, E> valueFactory, ExceptionHandler<E> exceptionHandler) {
this.valueFactory = valueFactory;
this.exceptionHandler = exceptionHandler;
}
@Override
public CallableSnapshot<V, E> load(K key) {
try {
V value = valueFactory.create(key);
return new CallableSnapshot<>(() -> value, exceptionHandler);
} catch (Exception e) {
return CallableSnapshot.createWithException(exceptionHandler.handle(e));
}
}
}
}