package com.limegroup.gnutella.tigertree;
import java.util.ArrayList;
import java.util.List;
import org.limewire.collection.FixedsizeForgetfulHashMap;
import com.google.inject.Singleton;
import com.limegroup.gnutella.security.Tiger;
/**
* Manages access to the list of full nodes for a HashTree.
* This tries to keep a maximum amount of nodes in memory, purging
* the least recently used items when the threshold is reached.
*/
@Singleton
class HashTreeNodeManagerImpl implements HashTreeNodeManager {
/**
* The maximum amount of nodes to store in memory.
*
* This will use up MAX_NODES * 24 + overhead bytes of memory.
*
* This number MUST be greater than the maximum possible number
* of nodes for the largest depth this stores. Currently
* we store up to depth 7, which has a maximum node count of 127
* nodes.
*/
private static final int MAX_NODES = 500;
/**
* Mapping of Tree to all nodes in that tree.
*
* FixedsizeForgetfulHashMap is used because it keeps track
* of which elements are most recently used, and provides a handy
* "removeLRUEntry()" method.
* The fixed-size portion is not used and is instead handled
* by the maximum node size externally calculated.
*/
private FixedsizeForgetfulHashMap<HashTree, List<List<byte[]>>> MAP =
new FixedsizeForgetfulHashMap<HashTree, List<List<byte[]>>>(MAX_NODES/2); // will never hit max.
/**
* The current amount of nodes stored in memory.
*/
private int _currentNodes = 0;
/* (non-Javadoc)
* @see com.limegroup.gnutella.tigertree.HashTreeNodeManager#getAllNodes(com.limegroup.gnutella.tigertree.HashTree)
*/
public List<List<byte[]>> getAllNodes(HashTree tree) {
int depth = tree.getDepth();
if(tree.getDepth() == 0) {
// trees of depth 0 have only one row.
List<List<byte[]>> outer = new ArrayList<List<byte[]>>(1);
outer.add(tree.getNodes());
return outer;
} else if (depth <2 || depth >= 7)
// trees of depth 1 & 2 are really easy to calculate, so
// always do those on the fly.
// trees deeper than 7 take up too much memory to store,
// so don't store them.
return HashTreeUtils.createAllParentNodes(tree.getNodes(), new Tiger());
else
// other trees need to battle it out for storage.
return getAllNodesImpl(tree);
}
/* (non-Javadoc)
* @see com.limegroup.gnutella.tigertree.HashTreeNodeManager#register(com.limegroup.gnutella.tigertree.HashTree, java.util.List)
*/
public void register(HashTree tree, List<List<byte[]>> nodes) {
// don't register depths 0-2 and 7-11
int depth = tree.getDepth();
if(depth > 2 && depth < 7 && !MAP.containsKey(tree))
insertEntry(tree, nodes);
}
/**
* Returns all intermediary nodes for the tree.
*
* If the item already existed in the map, this refreshes that item
* so that it is 'new' and then immediately returns it.
* If the item did not already exist, this may purge the oldest items
* from the map until enough room is available for this list of nodes
* to be added.
*/
private synchronized List<List<byte[]>> getAllNodesImpl(HashTree tree) {
List<List<byte[]>> nodes = MAP.get(tree);
if(nodes != null) {
// Make sure the map remembers that we want this entry.
MAP.put(tree, nodes);
return nodes;
}
nodes = HashTreeUtils.createAllParentNodes(tree.getNodes(), new Tiger());
insertEntry(tree, nodes);
return nodes;
}
/**
* Inserts the given entry into the Map, possibly purging older entries
* in order to make room.
*/
private synchronized void insertEntry(HashTree tree, List<List<byte[]>> nodes) {
int size = calculateSize(nodes);
while(_currentNodes + size > MAX_NODES) {
if(MAP.isEmpty())
throw new IllegalStateException(
"current: " + _currentNodes + ", size: " + size);
purgeLRU();
}
_currentNodes += size;
MAP.put(tree, nodes);
}
/**
* Purges the least recently used items from the map, decreasing
* the _currentNodes size.
*/
private synchronized void purgeLRU() {
List<List<byte[]>> nodes = MAP.removeLRUEntry().getValue();
_currentNodes -= calculateSize(nodes);
}
/**
* Determines how many entries are within each list in this list.
*/
private static int calculateSize(List<List<byte[]>> nodes) {
int size = 0;
for(List<byte[]> next : nodes)
size += next.size();
return size;
}
}