Routing-Packets-In-A-Network-Overlay / src / main / java / csx55 / overlay / util / OverlayCreator.java
OverlayCreator.java
Raw
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());
        }
    }
}