bali-component-framework
Version:
This library provides a JavaScript based implementation of the Bali Nebula™ Component Framework.
116 lines (106 loc) • 4.88 kB
JavaScript
/************************************************************************
* Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. *
************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. *
* *
* This code is free software; you can redistribute it and/or modify it *
* under the terms of The MIT License (MIT), as published by the Open *
* Source Initiative. (See http://opensource.org/licenses/MIT) *
************************************************************************/
;
/**
* This class implements a finite state machine. It defines the possible states of the
* machine and allowed transitions between states given a finite set of possible event
* types.
*/
// PUBLIC FUNCTIONS
/**
* This function creates a new finite state machine using the specified event type
* array and state transition object.
* <pre>
* eventTypes: ['$event1', '$event2', ... '$eventM']
* nextStates: {
* $state1: [undefined, '$state2', ... undefined]
* $state2: ['$state3', '$stateN', ... '$state1']
* ...
* $stateN: ['$state1', undefined, ... '$state3']
* }
* </pre>
* The first state in the nextStates object is the initial state of the finite state machine.
*
* An optional debug argument may be specified that controls the level of debugging that
* should be applied during execution. The allowed levels are as follows:
* <pre>
* 0: no debugging is applied (this is the default value and has the best performance)
* 1: log any exceptions to console.error before throwing them
* 2: perform argument validation checks on each call (poor performance)
* 3: log interesting arguments, states and results to console.log
* </pre>
*
* @param {Array} eventTypes An array of the possible event types as strings.
* @param {Object} nextStates An object defining the possible states as strings and allowed
* transitions between them given specific event types.
* @param {String} currentState The optional current state of the machine.
* @returns {Controller} A new finite state machine.
*/
const Controller = function(eventTypes, nextStates, currentState, debug) {
debug = debug || 0;
if (!Array.isArray(eventTypes) || typeof nextStates !== 'object') {
const exception = Error('One of the parameters to the constructor is not the right type.');
if (debug > 0) console.error(exception);
throw exception;
}
const numberOfEventTypes = eventTypes.length;
if (numberOfEventTypes === 0 || Object.keys(nextStates).length === 0) {
const exception = Error('The state machine must have at least one state and one event.');
if (debug > 0) console.error(exception);
throw exception;
}
for (const event in eventTypes) {
if (typeof event !== 'string') {
const exception = Error('Each event must be of type string.');
if (debug > 0) console.error(exception);
throw exception;
}
}
for (const state in nextStates) {
if (typeof state !== 'string') {
const exception = Error('Each state must be of type string.');
if (debug > 0) console.error(exception);
throw exception;
}
if (nextStates[state].length !== numberOfEventTypes) {
const exception = Error('Each next state list must have the same length as the number of event types.');
if (debug > 0) console.error(exception);
throw exception;
}
nextStates[state].forEach(function(transition) {
if (transition && Object.keys(nextStates).indexOf(transition) < 0) {
const exception = Error('A next state was found that is not in the possible states: ' + transition);
if (debug > 0) console.error(exception);
throw exception;
}
}, this);
}
currentState = currentState || Object.keys(nextStates)[0]; // defaults to first state
this.getState = function() {
return currentState;
};
this.validateEvent = function(event) {
const index = eventTypes.indexOf(event);
if (!nextStates[currentState][index]) {
const exception = Error('The event is not allowed in the current state: ' + event);
if (this.debug > 0) console.error(exception);
throw exception;
}
};
this.transitionState = function(event) {
const index = eventTypes.indexOf(event);
this.validateEvent(event);
currentState = nextStates[currentState][index];
return currentState;
};
return this;
};
Controller.prototype.constructor = Controller;
exports.Controller = Controller;