package com.limegroup.gnutella.tigertree; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.limegroup.gnutella.util.FixedsizeForgetfulHashMap; /** * 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. */ class HashTreeNodeManager { private static final HashTreeNodeManager INSTANCE = new HashTreeNodeManager(); public static HashTreeNodeManager instance() { return INSTANCE; } private 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 /* of HashTree -> List */ MAP = new FixedsizeForgetfulHashMap(MAX_NODES/2); // will never hit max. /** * The current amount of nodes stored in memory. */ private int _currentNodes = 0; /** * Returns all intermediary nodes for the tree. */ List /* of List of byte[] */ getAllNodes(HashTree tree) { int depth = tree.getDepth(); if(tree.getDepth() == 0) { // trees of depth 0 have only one row. List outer = new ArrayList(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 HashTree.createAllParentNodes(tree.getNodes()); else // other trees need to battle it out for storage. return getAllNodesImpl(tree); } /** * Registers the given list of nodes for the tree. */ void register(HashTree tree, List 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 getAllNodesImpl(HashTree tree) { List nodes = (List)MAP.get(tree); if(nodes != null) { // Make sure the map remembers that we want this entry. MAP.put(tree, nodes); return nodes; } nodes = HashTree.createAllParentNodes(tree.getNodes()); 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 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 nodes = (List)MAP.removeLRUEntry(); _currentNodes -= calculateSize(nodes); } /** * Determines how many entries are within each list in this list. */ private static int calculateSize(List /* of List */ nodes) { int size = 0; for(Iterator i = nodes.iterator(); i.hasNext(); ) size += ((List)i.next()).size(); return size; } }