/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.datasource;
import org.apache.commons.collections4.map.LRUMap;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.ByteArrayMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Caches entries get/updated and use LRU algo to purge them if the number
* of entries exceeds threshold.
*
* This implementation could extended to estimate cached data size for
* more accurate size restriction, but if entries are more or less
* of the same size the entries count would be good enough
*
* Another implementation idea is heap sensitive read cache based on
* SoftReferences, when the cache occupies all the available heap
* but get shrink when low heap
*
* Created by Anton Nashatyrev on 05.10.2016.
*/
public class ReadCache<Key, Value> extends AbstractCachedSource<Key, Value> {
private final Value NULL = (Value) new Object();
private Map<Key, Value> cache;
private boolean byteKeyMap;
public ReadCache(Source<Key, Value> src) {
super(src);
withCache(new HashMap<Key, Value>());
}
/**
* Installs the specific cache Map implementation
*/
public ReadCache<Key, Value> withCache(Map<Key, Value> cache) {
byteKeyMap = cache instanceof ByteArrayMap;
this.cache = Collections.synchronizedMap(cache);
return this;
}
/**
* Sets the max number of entries to cache
*/
public ReadCache<Key, Value> withMaxCapacity(int maxCapacity) {
return withCache(new LRUMap<Key, Value>(maxCapacity) {
@Override
protected boolean removeLRU(LinkEntry<Key, Value> entry) {
cacheRemoved(entry.getKey(), entry.getValue());
return super.removeLRU(entry);
}
});
}
// the guard against incorrect Map implementation for byte[] keys
private boolean checked = false;
private void checkByteArrKey(Key key) {
if (checked) return;
if (key instanceof byte[]) {
if (!byteKeyMap) {
throw new RuntimeException("Wrong map/set for byte[] key");
}
}
checked = true;
}
@Override
public void put(Key key, Value val) {
checkByteArrKey(key);
if (val == null) {
delete(key);
} else {
cache.put(key, val);
cacheAdded(key, val);
getSource().put(key, val);
}
}
@Override
public Value get(Key key) {
checkByteArrKey(key);
Value ret = cache.get(key);
if (ret == NULL) {
return null;
}
if (ret == null) {
ret = getSource().get(key);
cache.put(key, ret == null ? NULL : ret);
cacheAdded(key, ret);
}
return ret;
}
@Override
public void delete(Key key) {
checkByteArrKey(key);
Value value = cache.remove(key);
cacheRemoved(key, value);
getSource().delete(key);
}
@Override
protected boolean flushImpl() {
return false;
}
public synchronized Collection<Key> getModified() {
return Collections.emptyList();
}
@Override
public boolean hasModified() {
return false;
}
@Override
public synchronized Entry<Value> getCached(Key key) {
Value value = cache.get(key);
return value == null ? null : new SimpleEntry<>(value == NULL ? null : value);
}
/**
* Shortcut for ReadCache with byte[] keys. Also prevents accidental
* usage of regular Map implementation (non byte[])
*/
public static class BytesKey<V> extends ReadCache<byte[], V> implements CachedSource.BytesKey<V> {
public BytesKey(Source<byte[], V> src) {
super(src);
withCache(new ByteArrayMap<V>());
}
public ReadCache.BytesKey<V> withMaxCapacity(int maxCapacity) {
withCache(new ByteArrayMap<V>(new LRUMap<ByteArrayWrapper, V>(maxCapacity) {
@Override
protected boolean removeLRU(LinkEntry<ByteArrayWrapper, V> entry) {
cacheRemoved(entry.getKey().getData(), entry.getValue());
return super.removeLRU(entry);
}
}));
return this;
}
}
}