/*
* 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 java.util.AbstractMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import com.facebook.collections.TranslatingIterator;
import com.facebook.collectionsbase.Mapper;
import com.facebook.util.exceptions.ExceptionHandler;
@SuppressWarnings({"unchecked"})
public class CoreConcurrentCache<K, V, E extends Exception>
implements ConcurrentCache<K, V, E> {
private final ConcurrentMap<K, Object> cache;
private final ValueFactory<K, V, E> valueFactory;
private final ExceptionHandler<E> exceptionHandler;
/**
* allows subclasses to provide an alternative cache implementation
*
* @param valueFactory
* @param exceptionHandler
* @param cache - any ConcurrentMap impl will suffice
*/
protected CoreConcurrentCache(
ValueFactory<K, V, E> valueFactory,
ExceptionHandler<E> exceptionHandler,
ConcurrentMap<K, Object> cache
) {
this.valueFactory = valueFactory;
this.exceptionHandler = exceptionHandler;
this.cache = cache;
}
public CoreConcurrentCache(
ValueFactory<K, V, E> valueFactory, ExceptionHandler<E> exceptionHandler
) {
this(valueFactory, exceptionHandler, new ConcurrentHashMap<>());
}
@Override
public V get(final K key) throws E {
Object value = cache.get(key);
// if there isn't entry, do a thread-safe insert into the cache,
// and create if necessary
if (value == null) {
AtomicReference<Object> valueRef = new AtomicReference<>();
value = new PrivateFutureHelper<>(
() -> {
V producedValue = valueFactory.create(key);
// we place our value into the map in place of the factory if and
// only if it is still mapped to the same private future helper
CoreConcurrentCache.this.cache.replace(
key, valueRef.get(), producedValue
);
return producedValue;
},
exceptionHandler
);
valueRef.set(value);
Object existingValue = cache.putIfAbsent(key, value);
// did another thread insert a value into the cache before us? If so,
// use it
if (existingValue != null) {
value = existingValue;
}
}
return decodeValue(value);
}
@Override
public V put(K key, V value) throws E {
Object existingValue = cache.put(key, value);
return decodeValue(existingValue);
}
@Override
public V remove(K key) throws E {
Object value = cache.remove(key);
if (value == null) {
return null;
} else {
return decodeValue(value);
}
}
@Override
public boolean removeIfError(K key) {
Object value = cache.get(key);
if (value != null &&
value instanceof PrivateFutureHelper &&
((FutureHelper<V, E>)value).isError()
) {
cache.remove(key, value);
return true;
}
return false;
}
@Override
public void clear() {
cache.clear();
}
@Override
public void prune() {
// no-op
}
@Override
public int size() {
return cache.size();
}
@Override
public Iterator<Map.Entry<K, CallableSnapshot<V, E>>> iterator() {
return new TranslatingIterator<>(
new ValueMapper(),
cache.entrySet().iterator()
);
}
@Override
public CallableSnapshot<V, E> getIfPresent(K key) {
Object value = cache.get(key);
if (value == null) {
return null;
} else {
return new CallableSnapshot<>(
new CallableFutureHelper(value),
new CastingExceptionHandler<E>()
);
}
}
/**
* executes a FutureHelper to get a value from a cache entry if need be
*
* @param value cache entry to decode
* @return actual value in the cache
* @throws E on error producing the value
*/
private V decodeValue(Object value) throws E {
if (value instanceof PrivateFutureHelper) {
return ((FutureHelper<V, E>) value).safeGet();
} else {
return (V)value;
}
}
private class ValueMapper implements
Mapper<Map.Entry<K, Object>, Map.Entry<K, CallableSnapshot<V, E>>> {
@Override
public Map.Entry<K, CallableSnapshot<V, E>> map(Map.Entry<K, Object> input) {
return new AbstractMap.SimpleImmutableEntry<>(
input.getKey(),
new CallableSnapshot<>(
new CallableFutureHelper(input.getValue()),
new CastingExceptionHandler<E>() // OK to cast b/c know exception type
)
);
}
}
private class CallableFutureHelper implements Callable {
private final Object value;
private CallableFutureHelper(Object value) {
this.value = value;
}
@Override
public V call() throws Exception {
return decodeValue(value);
}
}
/**
* this is a marker class only. Effectively we are using the class type of
* this object in our cache to indicate that we need to call
* FutureHelper.safeGet() to produce a value. Obviously, being a private
* class, no one can create a value of this type, so...
*
* @param <V2>
* @param <E2>
*/
private static class PrivateFutureHelper<V2, E2 extends Exception>
extends FutureHelper<V2, E2>{
private PrivateFutureHelper(
Callable<V2> callable,
ExceptionHandler<E2> exceptionHandler
) {
super(callable, exceptionHandler);
}
}
}