package csx55.overlay.util; import java.util.HashMap; import java.util.ArrayList; import java.io.IOException; import java.util.HashSet; import java.util.Map; import csx55.overlay.transport.TCPSenderThread; import csx55.overlay.wireformats.LinkWeights; import csx55.overlay.wireformats.MessagingNodesList; public class OverlayCreator { /** * This is an inner Node class that is used to create the graph. Primarily, * it is used to store the sender thread, list of peers, and a set of nodes * that have already been evaluated by either this node or one of its peers. */ public class Node { public String connectionInfo; private TCPSenderThread sender; private ArrayList<String> neighbors; private HashSet<String> alreadyAdded; private int numNeighbors; public Node(String info, TCPSenderThread sender) { this.connectionInfo = info; this.sender = sender; neighbors = new ArrayList<>(); alreadyAdded = new HashSet<>(); numNeighbors = 0; } //checks to see if the alreadyAdded set contains the node n public boolean containsNeighbor(String n) { return alreadyAdded.contains(n); } //adds n to the list of neighbors public void addNeighbor(String n) { neighbors.add(n); } //returns a string in the format - ipPort: peer1, peer2, peer3, ... public String toString() { String str = connectionInfo + ": "; for (String s: neighbors) { str += s + ", "; } return str; } //adds to the alreadyAdded set public void addToAlreadyAdded(String node) { this.alreadyAdded.add(node); } //gets the number of neighbors public int getNumNeighbors() { return this.numNeighbors; } //increments the neighbor count public void incrementNumNeighbors() { this.numNeighbors++; } //returns the list of neighbors public ArrayList<String> getNeighbors() { return this.neighbors; } } /** * Checks to see if the preconditions for building the topology are true and calls the createGraph() method. * After building the topology, it creates an instance of LinkWeights with the topology passed in, which * assigns each edge in the graph with a random weight. Thereafter, it sends all MessagingNodes a * MessagingNodesList event with their neighbors. */ public LinkWeights createOverlay(Map<String, Object[]> nodes, int numEdges) throws Exception { int numConnections = nodes.size(); if (numConnections == 0) throw new Exception("No nodes are conencted to the registry"); if (numEdges == 0) throw new Exception("Number of edges cannot be 0"); if (numConnections < numEdges + 1) throw new Exception("Number of nodes must be greater than num of edges + 1"); Node[] topology = createGraph(nodes, numEdges); sendtoMessagingNodes(topology); LinkWeights weights = new LinkWeights(topology); return weights; } /** * This method will accept a Map of identifiers in the ip:port format and the number of edges * to connect to when creating the regular graph. For example, if 2 is passed in, this method * will create a graph where every node is connected to 2 other peers. This is done using a list * of Nodes, which all have a neighbors list that is used to store the neighbors the nodes should * connect to and an alreadyAdded set to help avoid duplicating links. This ensures that the list * of peers sent to each node contains the nodes they should connect to and no other. Some nodes * might get no nodes to connect to. In this case, they will only accept registration requests * from the nodes that try to connect to them. */ private Node[] createGraph(Map<String, Object[]> nodes, int numEdges) { int totalNodes = nodes.size(); Node[] topology = new Node[totalNodes]; String[] ipPorts = new String[totalNodes];//used for accessing nodes in the same indexing easily //loop over the nodes map and create nodes using the Nodes class int i = 0; for (HashMap.Entry<String, Object[]> node: nodes.entrySet()) { ipPorts[i] = node.getKey();//add node info the ipPorts array topology[i] = new Node(node.getKey(), (TCPSenderThread)node.getValue()[0]);//add them to the topology array i++; } //to ensure that no the graph is connected, create links in a loop where each node //connects to the node next to them and the last connects to the first for (i = 0; i < totalNodes; i++) { int indexNextNode = (i + 1) % totalNodes;//use modulo to loop back to first when at last node if (topology[i].getNumNeighbors() == numEdges) break;//handles the case where there are only two nodes so only one edge is needed //get the next node and add to the current nodes neighbors and alreadyAdded //also add itself to alreadyAdded so there are no self loops //add the current node to the alreadyAdded set of the next node but not the neighbors list //increment neighbors count in current node and next node String nextNode = ipPorts[indexNextNode]; topology[i].addNeighbor(nextNode); topology[i].addToAlreadyAdded(nextNode); topology[i].addToAlreadyAdded(ipPorts[i]); topology[indexNextNode].addToAlreadyAdded(ipPorts[i]); topology[i].incrementNumNeighbors(); topology[indexNextNode].incrementNumNeighbors(); } //after the first loop, each node should have two neighbors but only one in their neighbors list; there will be three nodes in their alreadyAdded set //if numEdges is greater than 2, start again from the first node and loop through each node //if that node is not included in its alreadyAdded set and does not already have numEdges, add it to the neighbors list and do the same things as the 1st loop //this does not handle the case where numEdges are odd; this algorithm will not produce the best graph and some links will be missing for (i = 0; i < totalNodes; i++) { if (numEdges <= 2) break; for (int j = 0; j < totalNodes; j++) { if (topology[i].getNumNeighbors() == numEdges) break; if (topology[i].containsNeighbor(ipPorts[j]) || topology[j].getNumNeighbors() == numEdges) continue; else { String nextNode = ipPorts[j]; topology[i].addNeighbor(nextNode); topology[i].addToAlreadyAdded(nextNode); topology[j].addToAlreadyAdded(ipPorts[i]); topology[i].incrementNumNeighbors(); topology[j].incrementNumNeighbors(); } } } return topology; } /** * Loops through each of the peers in the neighbors array list for each node and sends that * list as a MessangingNodesList event to the messagingNodes. Uses the sender associated * with each node to send the byte stream.n */ private void sendtoMessagingNodes(Node[] topology) throws IOException { for (int i = 0; i < topology.length; i++) { ArrayList<String> neighbors = topology[i].neighbors; MessagingNodesList mnl = new MessagingNodesList(neighbors.size(), neighbors); topology[i].sender.sendData(mnl.getBytes()); } } }