Home Reference Source Repository

lib/vvwe.js

'use strict';

const SortedArray = require('sorted-cmp-array');
const VVwEEntry = require('./vvweentry.js');
const Pair = require('./pair.js');

/**
 * Version vector with exceptions.
 */
class VVwE {
    /**
     * @param {Object} e The entry chosen by the local site. One entry per site.
     */
    constructor(e) {
        this.local = new VVwEEntry(e);
        this.vector = new SortedArray(VVwEEntry.comparator);
        this.vector.insert(this.local);
    };

    /**
     * Clone of this vvwe.
     * @return {VVwE} A version vector with exceptions containing the same
     * entry; clock; exceptions triples.
     */
    clone () {
        const cloneVVwE = new VVwE(this.local.e);
        for (let i = 0; i < this.vector.arr.length; ++i) {
            cloneVVwE.vector.arr[i] = new VVwEEntry(this.vector.arr[i].e);
            cloneVVwE.vector.arr[i].v = this.vector.arr[i].v;
            for (let j = 0; j < this.vector.arr[i].x.length; ++j) {
                cloneVVwE.vector.arr[i].x.push(this.vector.arr[i].x[j]);
            };
            if (cloneVVwE.vector.arr[i].e === this.local.e) {
                cloneVVwE.local = cloneVVwE.vector.arr[i];
            };
        };
        return cloneVVwE;
    };

    /**
     * Get a version vector with exceptions using a JSON.
     * @return {VVwE} The version vector with exceptions extracted from the
     * JSON in argument.
     */
    static fromJSON (object) {
        const vvwe = new VVwE(object.local.e);
        for (let i = 0; i < object.vector.arr.length; ++i) {
            vvwe.vector.arr[i] = new VVwEEntry(object.vector.arr[i].e);
            vvwe.vector.arr[i].v = object.vector.arr[i].v;
            for (let j = 0; j < object.vector.arr[i].x.length; ++j) {
                vvwe.vector.arr[i].x.push(object.vector.arr[i].x[j]);
            };
            if (object.vector.arr[i].e === object.local.e) {
                vvwe.local = vvwe.vector.arr[i];
            };
        };
        return vvwe;
    };
    
    /**
     * Increment the entry of the vector on local update.
     * @return {Pair} A pair that uniquely identifies the operation.
     */
    increment () {
        this.local.increment();
        return new Pair(this.local.e, this.local.v); 
    };


    /**
     * Increment from a remote operation.
     * @param {Pair} ec The entry and clock of the received event to add
     * supposedly ready.
     */
    incrementFrom (ec) {
        // #0 find the entry within the array of VVwEntries
        const index = this.vector.indexOf(ec.e);
        if (index < 0) {
            // #1 if the entry does not exist, initialize and increment
            this.vector.insert(new VVwEEntry(ec.e));
            this.vector.arr[this.vector.indexOf(ec.e)].incrementFrom(ec.c);
        } else {
            // #2 otherwise, only increment
            this.vector.arr[index].incrementFrom(ec.c);
        };
    };
    

    /**
     * Check if the argument are causally ready regards to this vector.  
     * @param {Pair} ec The identifier, i.e., the site clock of the operation
     * that happened-before the current event.
     * @return {Boolean} true if the event is ready, i.e. the identifier has
     * already been integrated to this vector; false otherwise.
     */
    isReady (ec) {
        // #0 no ec, automatically ready
        if (typeof ec === 'undefined' || ec === null) { return true; };
        // #1 otherwise, check in the vector and exceptions
        const index = this.vector.indexOf(ec.e);
        return index >= 0 && ec.c <= this.vector.arr[index].v &&
            this.vector.arr[index].x.indexOf(ec.c) < 0;
    };

    /**
     * Check if the message contains information already delivered.
     * @param {Pair} ec the site clock to check.
     */
    isLower (ec) {
        return typeof ec !== 'undefined' && ec !== null && this.isReady(ec);
    };
    
    /**
     * Merges the version vector in argument with this.
     * @param {VVwE} other the other version vector to merge with.
     */
    merge (other) {
        for (let i = 0; i < other.vector.arr.length; ++i) {
            let entry = other.vector.arr[i];
            let index = this.vector.indexOf(entry);
            if (index < 0) {
                // #1 entry does not exist, fully copy it
                let newEntry = new VVwEEntry(entry.e);
                newEntry.v = entry.v;
                for (let j = 0; j < entry.x.length; ++j){
                    newEntry.x.push(entry.x[j]);
                };
                this.vector.insert(newEntry);
            } else {
                // #2 otherwise merge the entries
                let currEntry = this.vector.arr[index];
                // #2A remove the exception from our vector
                let j = 0;
                while (j < currEntry.x.length) {
                    if (currEntry.x[j]<entry.v &&
                        entry.x.indexOf(currEntry.x[j]) < 0) {
                        currEntry.x.splice(j, 1);
                    } else {
                        ++j;
                    };
                };
                // #2B add the new exceptions
                j = 0;
                while (j < entry.x.length) {
                    if (entry.x[j] > currEntry.v &&
                        currEntry.x.indexOf(entry.x[j]) < 0) {
                        currEntry.x.push(entry.x[j]);
                    };
                    ++j;
                };
                currEntry.v = Math.max(currEntry.v, entry.v);
            };
        };
    };
    
};

module.exports = VVwE;