//BufferHeader.java,CachedNodes.java
//
//This 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 2.1 of the License, or (at your option) any later version.
//
//This 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.
package rtree;
//package rtree;
import java.util.*;
import java.io.*;
import rtree.seeded.SdNode;
/**
<b>A circular linked list of cached nodes using hashtable ?!!</b>
<p>This class will wrap a list of recently used nodes.
If the requested node is in <tt>Hashtable</tt> of the class then the node would
be returned from the <tt>Hashtable</tt>, else it would be read from the
disk.
<br>This will be a static object in the class RTree. Therefore no matter how
many RTree objects you create they all would have one cache for all the files
the object handles.
TODO : keep a set that keeps all the nodes that are dirty. With each node registering themselves
hara when thhey are dity, this is maageable.
@author Prachuryya Barua
*/
class BufferHeader
{
int recent;//the most recently inserted node
int last;//the least recently inserted node
int size;//max size of the link list
Hashtable cache;
BufferHeader(int size,Hashtable ch)
{
this.cache = ch;
this.size = size;
}
void flush()
throws NodeWriteException
{
//if(!RTree.writeThr){
for (Iterator it = cache.values().iterator(); it.hasNext();){
NodeValue node = (NodeValue)it.next();
node.node.flush();
} // end of for (Iterator = .iterator(); .hasNext();)
//}
}
/*for a fresh key when the array is not full.This will be called when the
buffer is not fully warm*/
void put(int key, Node node)
{
try{
//the following two conditions happens in multithreaded programs
if(cache.containsKey(new Integer(key))){
update(key);
return;
}
if(cache.size() == size){
replace(key,node);
return;
}
if(cache.size() == 0){
last = key;
cache.put(new Integer(key), new NodeValue(node,key,key));
}else{
//remove recent
NodeValue tmpPrev = (NodeValue)(cache.remove(new Integer(recent)));
if(last == recent){//there is only one node in cache
cache.put(new Integer(key), new NodeValue(node,tmpPrev.next,tmpPrev.next));
cache.put(new Integer(recent), new NodeValue(tmpPrev.node,key,key));
}
else{
//remove next of previous
NodeValue tmpPNext = (NodeValue)(cache.remove(new Integer(tmpPrev.next)));
cache.put(new Integer(key), new NodeValue(node,tmpPrev.next,recent));
cache.put(new Integer(tmpPrev.next), new NodeValue(tmpPNext.node,tmpPNext.next,key));
cache.put(new Integer(recent), new NodeValue(tmpPrev.node,key,tmpPrev.prev));
}
}
recent = key;
}
catch(Exception e){
e.printStackTrace();
}
}
/*a new key in a filled array*/
void replace(int key,Node node)
throws NodeWriteException
{
try{
if(cache.containsKey(new Integer(key))){
update(key);
return;
}
if(cache.size() < size){
put(key,node);
return;
}
//remove the 'last' node
NodeValue lastNode = (NodeValue)(cache.remove(new Integer(last)));
lastNode.node.flush();
NodeValue pNode = (NodeValue)(cache.remove(new Integer(lastNode.prev)));
NodeValue nNode = (NodeValue)(cache.remove(new Integer(lastNode.next)));
//put back the three nodes
cache.put(new Integer(key), new NodeValue(node,lastNode.next, lastNode.prev));
cache.put(new Integer(lastNode.prev), new NodeValue(pNode.node,key,pNode.prev));
cache.put(new Integer(lastNode.next), new NodeValue(nNode.node,nNode.next,key));
recent = key;//this is the latest node
last = lastNode.next;//set the next in chain as the new 'last'
}
catch(Exception e){
e.printStackTrace();
}
}
/**make a node that is present as 'recent'*/
void update(int key)
{
try{
if(key != recent){
NodeValue node,nextNode,prevNode,rcntNode,lastNode;//temp variables
node = (NodeValue)(cache.remove(new Integer(key)));
if(node == null){//will not happen
System.out.println("CachedNodes.update: unlikely flow");
return;
}
if(key == last){
last = node.next;
cache.put(new Integer(key), new NodeValue(node.node, node.next, node.prev));
}
else{
//adjust next node and the recent node
nextNode = (NodeValue)(cache.remove(new Integer(node.next)));
if(recent != node.next){//if next node is not the recent node
rcntNode = (NodeValue)(cache.remove(new Integer(recent)));
rcntNode.next = key;
cache.put(new Integer(recent), new NodeValue(rcntNode.node, rcntNode.next,rcntNode.prev));
}
else{//next node is the recent node
nextNode.next = key;
}
cache.put(new Integer(node.next), new NodeValue(nextNode.node, nextNode.next,node.prev));
//adjust previous node and the last node - if unequal
prevNode = (NodeValue)(cache.remove(new Integer(node.prev)));
if(last != node.prev){//if last node is not the prev node
lastNode = (NodeValue)(cache.remove(new Integer(last)));
lastNode.prev = key;
cache.put(new Integer(last),new NodeValue(lastNode.node, lastNode.next,lastNode.prev));
}
else{//if the last node is the prev node.
prevNode.prev = key;
}
cache.put(new Integer(node.prev), new NodeValue(prevNode.node, node.next,prevNode.prev));
//put the new node
cache.put(new Integer(key), new NodeValue(node.node,last,recent));
}
//update local variables
}
recent = key;
}
catch(Exception e){
e.printStackTrace();
}
}
void remove(int key)
throws NodeWriteException
{
try{
NodeValue node = (NodeValue)(cache.remove(new Integer(key)));
if((cache.size() != 0) && (node != null)){
//if(!RTree.writeThr)
node.node.flush();
if(cache.size() == 1){
NodeValue oNode = (NodeValue)(cache.remove(new Integer(node.prev)));
cache.put(new Integer(node.prev), new NodeValue(oNode.node, node.prev,node.prev));
recent = last = node.prev;
}
else{
//if(!RTree.writeThr)
node.node.flush();
NodeValue pNode = (NodeValue)(cache.remove(new Integer(node.prev)));
NodeValue nNode = (NodeValue)(cache.remove(new Integer(node.next)));
cache.put(new Integer(node.prev), new NodeValue(pNode.node, node.next,pNode.prev));
cache.put(new Integer(node.next), new NodeValue(nNode.node, nNode.next,node.prev));
if(key == recent)
recent = node.prev;
if(key == last)
last = node.next;
}
}
}
catch(Exception e){
e.printStackTrace();
}
}
void reset()
throws NodeWriteException
{
//if(!RTree.writeThr)
flush();
cache.clear();
}
}
public class CachedNodes
{
private static final int NODE = 0;
private static final int SDNODE = 1;
Hashtable cache;
BufferHeader buffHeader;
int size = Node.CACHE_SIZE;
CachedNodes()
{
//System.out.println("CachedNodes : cache called");
cache = new Hashtable(Node.CACHE_SIZE+1,1);
buffHeader = new BufferHeader(Node.CACHE_SIZE,cache);
size = Node.CACHE_SIZE;
}
/**
This one is still under construction.
*/
CachedNodes(int size)
{
if(size < 0)
throw new IllegalArgumentException("CachedNodes:: size is less than zero");
cache = new Hashtable(size+1,1);
buffHeader = new BufferHeader(size, cache);
this.size = size;
}
public synchronized void setCacheSize(int size)
throws NodeWriteException
{
if(size < 0)
throw new IllegalArgumentException("CachedNodes:: size is less than zero");
removeAll();
cache = new Hashtable(size+1,1);
buffHeader = new BufferHeader(size, cache);
this.size = size;
}
public synchronized int getSize()
{
return cache.size();
}
private Node getNode(RandomAccessFile file,String fileName,long lndIndex,FileHdr flHdr, int type)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
int ndIndex = (int)lndIndex;
int key = calKey(fileName,ndIndex);
NodeValue node = (NodeValue)(cache.get(new Integer(key)));
Node nNode;
if(node == null){//Node not in cache
if(type == NODE){
nNode = new Node(file, fileName, ndIndex, flHdr);
}else{
nNode = new SdNode(file, fileName, ndIndex, flHdr);
}
key = calKey(fileName, (int)nNode.getNodeIndex());//this is for the case where index is NOT_DEFINED
nNode.sweepSort();
//cache not full
if(cache.size() < Node.CACHE_SIZE){
buffHeader.put(key,nNode);//(Node)nNode.clone());
}else if(cache.size() == Node.CACHE_SIZE){//cache Is full
buffHeader.replace(key,nNode);//(Node)nNode.clone());
}
return nNode;
}
else{//node found in the cache
buffHeader.update(key);
node.node.sweepSort();
return (node.node);
}
}
private Node getNode(RandomAccessFile file,String fileName,long parentIndex, int elmtType, FileHdr flHdr,
int type)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
Node nNode;
if(type == NODE){
nNode = new Node(file,fileName,parentIndex, elmtType, flHdr);
}else{
nNode = new SdNode(file,fileName,parentIndex, elmtType, flHdr);
}
int key = calKey(fileName, (int)nNode.getNodeIndex());
nNode.sweepSort();
//cache not full
if(cache.size() < Node.CACHE_SIZE)
buffHeader.put(key,nNode);
//cache Is full
else if(cache.size() == Node.CACHE_SIZE)
buffHeader.replace(key,nNode);
return nNode;
}
//-----------------------Methods for client to get Node they prefer------------
/**
This one returns an existing <code>SdNode</code>
*/
public synchronized SdNode getSdNode(RandomAccessFile file,String fileName,long lndIndex,FileHdr flHdr)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
return (SdNode)getNode(file,fileName,lndIndex,flHdr, SDNODE);
}
/**
This one returns an existing <code>SdNode</code>.
*/
public synchronized SdNode getSdNode(RandomAccessFile file,String fileName,long parentIndex,
int elmtType, FileHdr flHdr)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
return (SdNode)getNode(file,fileName,parentIndex, elmtType, flHdr, SDNODE);
}
/**
This one returns an existing <code>Node</code>.
*/
public synchronized Node getNode(RandomAccessFile file,String fileName,long lndIndex,FileHdr flHdr)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
return getNode(file,fileName,lndIndex,flHdr, NODE);
}
/**
This one returns an new <code>SdNode</code>.
*/
public synchronized Node getNode(RandomAccessFile file,String fileName,long parentIndex,
int elmtType, FileHdr flHdr)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
return getNode(file,fileName,parentIndex, elmtType, flHdr, NODE);
}
/**
This one returns an new <code>Node</code>.
*/
public synchronized Node getNode(RandomAccessFile file,String fileName,long parentIndex,
int elmtType, FileHdr flHdr, Node type)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
if(type instanceof SdNode)
return getNode(file,fileName,parentIndex, elmtType, flHdr, SDNODE);
else
return getNode(file,fileName,parentIndex, elmtType, flHdr, NODE);
}
/**
This one return <code>ReadNode</code> a read only node.
All clients that need only to query the rtree must at all cost call this method. This method will
return a clones ReadNode, so that concurrent reads can take place (because none of the methods
of <code>Node</code> are <code>synchronized</code>.
*/
public synchronized ReadNode getReadNode(RandomAccessFile file,String fileName,long lndIndex,FileHdr flHdr)
throws IllegalValueException, NodeReadException, FileNotFoundException, IOException, NodeWriteException
{
return ReadNode.makeReadNode(getNode(file,fileName,lndIndex,flHdr));
}
/**
Write all the diry nodes to the disc.
*/
synchronized void flush()
throws NodeWriteException
{
//if(!RTree.writeThr)
buffHeader.flush();
}
/**
This method would be called only by those threads that need to modify the
tree. Hence this method is automatically synchronized.
*/
synchronized void remove(String fileName,long ndIndex)
throws NodeWriteException
{
int key = calKey(fileName,(int)ndIndex);
buffHeader.remove(key);
}
synchronized void removeAll()
throws NodeWriteException
{
buffHeader.reset();
}
static Map<String, Integer> fileNamesMap = new LinkedHashMap<String, Integer>();
static void clearFileNamesMap(){
fileNamesMap.clear();
}
int calKey(String fileName,int idx)
{
if(fileName != null) {
Integer i = fileNamesMap.get(fileName);
if(i == null){
if(fileNamesMap.size() > 1023){
throw new ArrayIndexOutOfBoundsException();
}
fileNamesMap.put(fileName, fileNamesMap.size());
i = fileNamesMap.get(fileName);
}
// System.out.println(idx + " " + fileName + " " + ((idx << 5)+ fileName.toLowerCase().hashCode() % 32));
return ((idx << 10)+ i);
} else{
System.out.println("CachedNodes.calKey: fileName null");
return 0;
}
}
}
class NodeValue
{
Node node;
int next;//the next node's key
int prev;//the prev node's key
NodeValue(Node node,int n,int p)
{
this.node = node;
next = n;
prev = p;
}
}
/**
TODO:
2) A way to pin an node. Obviously the client must also unpinn the node.
*/