Lewerow/Zurvan

View on GitHub
detail/TimerInterceptor.js

Summary

Maintainability
A
55 mins
Test Coverage
A
100%
'use strict';
var FieldOverrider = require('./utils/FieldOverrider');
var TimerRepository = require('./TimerRepository');
var TypeChecks = require('./utils/TypeChecks');

function Callback(f, args) {
  this.f = f;
  this.args = args;
}

Callback.prototype.call = function() {
  this.f.apply(undefined, this.args);
};

function TimerInterceptor(timeServer, timerType) {
  this.timeServer = timeServer;
  this.timerType = timerType;
}

TimerInterceptor.prototype.intercept = function(config, uidGenerator) {
  this.config = config;
  this.timerRepository = new TimerRepository(this.config, uidGenerator);
  this.setTimers = new FieldOverrider(
    this.timerType.context,
    this.timerType.setName,
    this.addTimer.bind(this, this.timerType.type)
  );
  this.clearTimers = new FieldOverrider(
    this.timerType.context,
    this.timerType.clearName,
    this.clearTimer.bind(this)
  );

  if (this.config.fakeNodeDedicatedTimers) {
    var nodeTimers = require('timers');

    this._nodeSetterOverrider = new FieldOverrider(
      nodeTimers,
      this.timerType.setName,
      this.addTimer.bind(this, this.timerType.type)
    );
    this._nodeClearerOverrider = new FieldOverrider(
      nodeTimers,
      this.timerType.clearName,
      this.clearTimer.bind(this)
    );
  }
};

TimerInterceptor.prototype.release = function() {
  this.setTimers.restore();
  this.clearTimers.restore();

  if (this.config.fakeNodeDedicatedTimers) {
    this._nodeSetterOverrider.restore();
    this._nodeClearerOverrider.restore();
  }

  return this.timerRepository.releaseAll();
};

TimerInterceptor.prototype.nextTimers = function() {
  return this.timerRepository.nextTimers();
};

TimerInterceptor.prototype.lastTimer = function() {
  return this.timerRepository.lastTimer();
};

TimerInterceptor.prototype.createCallback = function(callback, args) {
  if (TypeChecks.isFunction(callback)) {
    return new Callback(callback, args);
  }

  if (this.config.acceptEvalTimers) {
    return new Callback(function() {
      /*jshint -W061 */
      return eval(callback);
    }, []);
  }

  throw new Error(
    'Node.js does not accept strings to be evaluated in timers. If you wish, you can configure Zurvan to use them, but beware.'
  );
};

var MINIMUM_CALL_DELAY = 1;
var MAX_CALL_DELAY = Math.pow(2, 31) - 1; // int32 boundary

function timerCreateErrorMessage(mustBe, given) {
  return (
    'Call delay in timer call must be ' + mustBe + ' given: << ' + given + ' >>'
  );
}

TimerInterceptor.prototype.createCallDelay = function(requestedCallDelay) {
  if (!TypeChecks.isNumber(requestedCallDelay)) {
    if (this.config.denyImplicitTimer) {
      throw new Error(
        timerCreateErrorMessage('a numeric value', requestedCallDelay)
      );
    }

    return MINIMUM_CALL_DELAY;
  }

  if (requestedCallDelay < MINIMUM_CALL_DELAY) {
    if (this.config.denyTimersShorterThan1Ms) {
      throw new Error(
        timerCreateErrorMessage('>= ' + MINIMUM_CALL_DELAY, requestedCallDelay)
      );
    }

    return MINIMUM_CALL_DELAY;
  }

  if (requestedCallDelay > MAX_CALL_DELAY) {
    if (this.config.denyTimersLongerThanInt32) {
      throw new Error(
        timerCreateErrorMessage('<= ' + MAX_CALL_DELAY, requestedCallDelay)
      );
    }

    return MINIMUM_CALL_DELAY;
  }

  return requestedCallDelay;
};

TimerInterceptor.prototype.addTimer = function(
  TimerType,
  requestedCallback,
  requestedCallDelay
) {
  var callback = this.createCallback(
    requestedCallback,
    [].splice.call(arguments, 3)
  );
  var callDelay = this.createCallDelay(requestedCallDelay);

  var timer = new TimerType(
    callback,
    this.timerRepository,
    this.timeServer.currentTime,
    callDelay
  );
  return this.timerRepository.insertTimer(timer);
};

TimerInterceptor.prototype.clearTimer = function(uid) {
  return this.timerRepository.clearTimer(uid);
};

module.exports = TimerInterceptor;