Source: visualization/hostPermutation.js

/**
 * The constructor for this abstract class will typically be invoked by concrete
 * sub-classes
 * 
 * @clasdesc
 * 
 * <p>
 * A HostPermutation is responsible for determining the order in which hosts
 * should be displayed in the visualization of the graph. This class is also
 * responsible for determining the hosts' visualization's color. This is because
 * Shiviz always uses colors in a fixed order for consistency, so host color is
 * tied to host permutation.
 * </p>
 * 
 * <p>
 * Typical usage involves adding hosts to order using
 * {@link HostPermutation#addGraph}, calling {@link HostPermutation#update} to
 * compute the ordering of hosts, and then using one of the getters to retrieve
 * the computed host color and order.
 * </p>
 * 
 * <p>
 * HostPermutation and all its subclasses must ensure that the computed host
 * order and colors do not change unless {@link HostPermutation#update} is
 * called. Before the very first time update is called, do not try to retrieve
 * the computed host color and order (i.e by using
 * {@link HostPermutation#getHosts}, etc)
 * </p>
 * 
 * <p>
 * HostPermutation is an abstract class. To implement a specific permutation, a
 * class that extends HostPermutation should be written and the
 * {@link HostPermutation#update} method should be overriden.
 * </p>
 * 
 * @constructor
 * @abstract
 * @param {Boolean} reverse If true, the ordering of hosts is reversed
 */
function HostPermutation(reverse) {

    if (this.constructor == HostPermutation) {
        throw new Exception("Cannot instantiate HostPermutation; HostPermutation is an abstract class");
    }

    /** @private */
    this.graphs = [];

    /** @private */
    this.hosts = [];

    /** @private */
    this.reverse = reverse;

    /** @private */
    this.color = d3.scaleOrdinal(d3.schemeCategory20);

    /** @private */
    this.hostColors = {};

    /** @private */
    this.hasUpdated = false;
}

/**
 * Adds a graph to the HostPermutation. HostPermutation determines an ordering
 * of hosts based on the hosts of graphs added using this method
 * 
 * @param {ModelGraph} graph The graph to add
 */
HostPermutation.prototype.addGraph = function(graph) {
    this.graphs.push(graph);
};

/**
 * Gets a list of hosts in the order determined by the HostPermutation. Note
 * that you must call {@link update} to compute the ordering of hosts before
 * retrieving it with this method
 * 
 * @returns {Array<String>} Array of hosts
 */
HostPermutation.prototype.getHosts = function() {
    if (!this.hasUpdated) {
        throw new Exception("HostPermutation.prototype.getHosts: You must call update() first");
    }
    return this.reverse ? this.hosts.slice().reverse() : this.hosts.slice();
};

/**
 * Gets a list of hosts in the order determined by the HostPermutation. Only
 * hosts contained in both this HostPermutation and the filter array will be
 * returned. Note that you must call {@link update} to compute the ordering of
 * hosts before retrieving it with this method.
 * 
 * @param {Array<String>} filter The filter array.
 * @returns {Array<String>} Array of hosts
 */
HostPermutation.prototype.getHostsAndFilter = function(filter) {
    if (!this.hasUpdated) {
        throw new Exception("HostPermutation.prototype.getHosts: You must call update() first");
    }

    var filterSet = {};
    for (var i = 0; i < filter.length; i++) {
        filterSet[filter[i]] = true;
    }

    var ret = [];
    for (var i = 0; i < this.hosts.length; i++) {
        if (filterSet[this.hosts[i]]) {
            ret.push(this.hosts[i]);
        }
    }

    if (this.reverse) {
        ret.reverse();
    }

    return ret;
};

/**
 * Gets the designated color of the specified host
 * 
 * @param {String} host The host whose color you want to retrieve
 * @returns {String} A valid color string.
 */
HostPermutation.prototype.getHostColor = function(host) {
    if (!this.hasUpdated) {
        throw new Exception("HostPermutation.prototype.getHosts: You must call update() first");
    }

    return this.hostColors[host];
};

/**
 * Returns all designated host colors
 * 
 * @returns {Object<String, String>} A mapping of host name to host color
 */
HostPermutation.prototype.getHostColors = function() {
    if (!this.hasUpdated) {
        throw new Exception("HostPermutation.prototype.getHosts: You must call update() first");
    }

    var colors = {};
    for (var host in this.hostColors) {
        colors[host] = this.hostColors[host];
    }
    return colors;
};

/**
 * <p>
 * The update method alone is responsible for figuring out the ordering of hosts
 * and for assigning host colors.
 * </p>
 * 
 * <p>
 * In its current form, because it is an abstract method, it only performs color
 * assignment. Classes that extend HostPermutation must be sure to override this
 * method and extend it with host permutation assignment functionality.
 * </p>
 * 
 * @abstract
 */
HostPermutation.prototype.update = function() {

    this.hasUpdated = true;

    for (var i = 0; i < this.graphs.length; i++) {
        var graph = this.graphs[i];
        var hosts = graph.getHosts();

        for (var j = 0; j < hosts.length; j++) {
            var host = hosts[j];
            if (!this.hostColors[host]) {
                this.hostColors[host] = this.color(host);
            }
        }
    }

};

/**
 * Constructs a LengthPermutation
 * 
 * @classdesc
 * 
 * LengthPermutation arranges hosts in ascending order based on the number of
 * LogEvents the host contains.
 * 
 * @constructor
 * @extends HostPermutation
 * @param {Boolean} reverse If true, the ordering of hosts is reversed
 */
function LengthPermutation(reverse) {
    HostPermutation.call(this, reverse);
}

// LengthPermutation extends HostPermutation
LengthPermutation.prototype = Object.create(HostPermutation.prototype);
LengthPermutation.prototype.constructor = LengthPermutation;

/**
 * Overrides {@link HostPermutation#update}
 */
LengthPermutation.prototype.update = function() {

    HostPermutation.prototype.update.call(this);

    var currHosts = {};

    for (var i = 0; i < this.graphs.length; i++) {
        var graph = this.graphs[i];
        var hosts = graph.getHosts();

        for (var j = 0; j < hosts.length; j++) {
            var host = hosts[j];
            var curr = graph.getHead(host).getNext();
            var count = 0;
            while (!curr.isTail()) {
                count++;
                curr = curr.getNext();
            }

            if (!currHosts[host] || count > currHosts[host].count) {
                currHosts[host] = {
                    count: count,
                    host: host
                };
            }

        }
    }

    var hostArray = [];
    for (var key in currHosts) {
        hostArray.push(currHosts[key]);
    }

    hostArray.sort(function(a, b) {
        return a.count - b.count;
    });

    this.hosts = [];
    for (var i = 0; i < hostArray.length; i++) {
        this.hosts.push(hostArray[i].host);
    }

};

/**
 * Constructs a LogOrderPermutation
 * 
 * @classdesc
 * 
 * LogOrderPermutation orders hosts based on the order they appear in logs.
 * 
 * @constructor
 * @extends HostPermutation
 * @param {Boolean} reverse If true, the ordering of hosts is reversed
 */
function LogOrderPermutation(reverse) {
    HostPermutation.call(this, reverse);

    /** @private */
    this.logs = [];
}

// LogOrderPermutation extends HostPermutation
LogOrderPermutation.prototype = Object.create(HostPermutation.prototype);
LogOrderPermutation.prototype.constructor = LogOrderPermutation;

/**
 * Adds logs. This class will order hosts based on the order they appear in the
 * logs provided.
 * 
 * @param {Array<LogEvent>} logs The logs to add
 */
LogOrderPermutation.prototype.addLogs = function(logs) {
    this.logs = this.logs.concat(logs);
};

/**
 * Overrides {@link HostPermutation#update}
 */
LogOrderPermutation.prototype.update = function() {

    HostPermutation.prototype.update.call(this);

    var hostSet = {};

    for (var i = 0; i < this.logs.length; i++) {
        var log = this.logs[i];
        if (!hostSet[log.getHost()]) {
            hostSet[log.getHost()] = true;
            this.hosts.push(log.getHost());
        }
    }
};