package org.limewire.mojito.visual.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.limewire.mojito.KUID;
import org.limewire.mojito.routing.Bucket;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.routing.RouteTable;
import org.limewire.mojito.routing.RouteTable.RouteTableEvent;
import org.limewire.mojito.routing.RouteTable.RouteTableEvent.EventType;
import org.limewire.mojito.visual.RouteTableGraphCallback;
import org.limewire.mojito.visual.components.BinaryEdge;
import org.limewire.mojito.visual.components.ContactVertex;
import org.limewire.mojito.visual.components.InteriorNodeVertex;
import org.limewire.mojito.visual.components.BinaryEdge.EdgeType;
import edu.uci.ics.jung.graph.ArchetypeVertex;
import edu.uci.ics.jung.graph.Vertex;
public class NodeGraph extends RouteTableGraph {
/**
* A constant for the maximum depth of the tree
*/
private static int MAX_DEPTH = 20;
private Bucket bucket;
public NodeGraph(RouteTable routeTable, RouteTableGraphCallback callback, Bucket bucket) {
super(routeTable, callback);
root = new InteriorNodeVertex();
tree = new RootableSparseTree(root);
this.bucket = bucket;
}
@Override
public String getGraphInfo() {
synchronized (routeTable) {
return bucket.toString();
}
}
/**
* The way to populate a node graph is to create a per-level Map of
* nodes, in order to visualize only the smallest common bit-prefix.
*
*/
@Override
public void populateGraph() {
//get a copy of the nodes
List<Contact> nodes = new ArrayList<Contact>(bucket.getActiveContacts());
if(nodes.isEmpty()) {
return;
}
if(nodes.size() < 2) {
createVertexForNode(nodes.get(0), 0);
return;
}
//for every node, take the longest common prefix to any other node
//and place the node in the tree at depth = common_prefix + 1
Contact curNode;
for(int i = 0; i < nodes.size(); i++) {
curNode = nodes.get(i);
insertNode(curNode, nodes);
}
}
private void insertNode(Contact node, Collection<Contact> nodeList) {
int longestPrefix = 0;
for(Contact cmpNode: nodeList) {
if(cmpNode.equals(node)) {
continue;
}
int commonPrefix = node.getNodeID().bitIndex(cmpNode.getNodeID());
if(commonPrefix > longestPrefix) {
longestPrefix = commonPrefix;
}
}
createVertexForNode(node, longestPrefix);
}
private void removeNode(Contact node) {
KUID nodeId = node.getNodeID();
InteriorNodeVertex vertex = (InteriorNodeVertex)root;
Vertex child;
for(int i= bucket.getDepth(); i <= MAX_DEPTH; i++) {
child = null;
if(nodeId.isBitSet(i)) {
child = vertex.getRightChild();
} else {
child = vertex.getLeftChild();
}
if(child == null) {
throw new IllegalStateException("Trying to remove a node that is not in the tree");
} else if(child instanceof ContactVertex) {
ContactVertex cv = (ContactVertex)child;
if(cv.getNode().equals(node)) {
removeRouteTableVertex(child);
} else if(i == MAX_DEPTH){
return;
} else {
throw new IllegalStateException("Found a contact node " +
"in the path of node to be removed");
}
} else {
vertex = (InteriorNodeVertex)child;
}
}
}
private ContactVertex createContactVertex(Contact contact, InteriorNodeVertex predecessor,
EdgeType type, int depth) {
ContactVertex cv = new ContactVertex(contact, depth + 1);
tree.addVertex(cv);
tree.addEdge(new BinaryEdge(predecessor, cv, type));
return cv;
}
private void createVertexForNode(Contact node, int depth) {
InteriorNodeVertex vertex = (InteriorNodeVertex)root;
KUID nodeId = node.getNodeID();
Vertex child;
EdgeType type;
//start at the bucket's depth: every bit before that is common to all
//the nodes in the bucket
for(int i= bucket.getDepth(); i <= depth ; i++) {
child = null;
if(nodeId.isBitSet(i)) {
child = vertex.getRightChild();
type = EdgeType.RIGHT;
} else {
child = vertex.getLeftChild();
type = EdgeType.LEFT;
}
if(child == null) {
//if we have arrive to leaf node, create it and return
//also create the node if we have reached the max depth.
if(i == depth || i == MAX_DEPTH) {
createContactVertex(node, vertex, type, depth);
return;
}
vertex = createInteriorNode(vertex, type);
} else if(child instanceof ContactVertex) {
//if we have reached max depth, it's normal that there is already a node
if(i == (MAX_DEPTH - 1)) {
return;
}
ContactVertex cv = (ContactVertex)child;
int commonPrefix = node.getNodeID().bitIndex(cv.getNode().getNodeID());
if(i > commonPrefix) {
throw new IllegalStateException("Existing contact vertex " +
"in the path of other contact");
}
removeRouteTableVertex(cv);
createVertexForNode(cv.getNode(), commonPrefix);
createVertexForNode(node, commonPrefix);
return;
} else {
vertex = (InteriorNodeVertex)child;
}
}
}
@Override
public String getLabelForVertex(ArchetypeVertex v) {
if(v.equals(root)) {
String title = bucket.getBucketID().toBinString().substring(0, bucket.getDepth());
return title+"...";
}
if(v instanceof ContactVertex) {
return v.toString()+"...";
}
return "";
}
public void handleRouteTableEvent(RouteTableEvent event) {
if (event.getEventType().equals(EventType.ADD_ACTIVE_CONTACT)) {
Bucket bucket = event.getBucket();
if(!bucket.equals(this.bucket)) {
return;
}
insertNode(event.getContact(), bucket.getActiveContacts());
callback.handleGraphLayoutUpdated();
} else if (event.getEventType().equals(EventType.REMOVE_CONTACT)) {
Bucket bucket = event.getBucket();
if(!bucket.equals(this.bucket)) {
return;
}
removeNode(event.getContact());
callback.handleGraphLayoutUpdated();
} else if (event.getEventType().equals(EventType.REPLACE_CONTACT)) {
Bucket bucket = event.getBucket();
if(!bucket.equals(this.bucket)) {
return;
}
removeNode(event.getExistingContact());
insertNode(event.getContact(), bucket.getActiveContacts());
callback.handleGraphLayoutUpdated();
} else if (event.getEventType().equals(EventType.UPDATE_CONTACT)) {
callback.handleGraphInfoUpdated();
} else if (event.getEventType().equals(EventType.SPLIT_BUCKET)) {
// This bucket has been split! Go back to bucket tree
callback.handleRouteTableCleared();
} else if (event.getEventType().equals(EventType.CLEAR)) {
callback.handleRouteTableCleared();
}
}
}