baja/obj/Dimension.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Gareth Johnson
 */

/**
 * Defines {@link baja.Dimension}.
 * @module baja/obj/Dimension
 */
define([ "bajaScript/sys",
        "bajaScript/baja/obj/Simple",
        "bajaScript/baja/obj/objUtil" ], function (
         baja,
         Simple,
         objUtil) {
  
  'use strict';
  
  var SYMBOLS = [ 'm', 'kg', 's', 'A', 'K', 'Mol', 'cd', '$' ],
      DECODE_STRING_REGEX = /(\(\S+?\))/g,
      DECODE_SEGMENT_REGEX = /\(([a-zA-Z$]+)(-?[\d.]*)\)/,

      subclass = baja.subclass,
      callSuper = baja.callSuper,

      cacheDecode = objUtil.cacheDecode,
      cacheEncode = objUtil.cacheEncode;

  /**
   * Convert a number to its exponent string representation.
   * 
   * @inner
   * @param {Number} num
   * @returns {String}
   */
  function toExponent(num) {
    switch (num) {
      case 1: return '';
      case 2: return '²';
      case 3: return '³';
      default: return num;
    }
  }

  /**
   * Represents a `baja:Dimension` in BajaScript.
   *
   * When creating a `Simple`, always use the `make()` method instead of
   * creating a new Object.
   *
   * @class
   * @alias baja.Dimension
   * @extends baja.Simple
   */
  var Dimension = function Dimension(meter, kilogram, second, ampere,
      kelvin, mole, candela, dollar) {
    
    callSuper(Dimension, this, arguments);
    
    this.$m = meter || 0;
    this.$kg = kilogram || 0;
    this.$s = second || 0;
    this.$A = ampere || 0;
    this.$K = kelvin || 0;
    this.$mol = mole || 0;
    this.$cd = candela || 0;
    this.$$ = dollar || 0;
  };

  subclass(Dimension, Simple);

  /**
   * Default `Dimension` instance.
   *
   * @type {baja.Dimension}
   */
  Dimension.DEFAULT = new Dimension();

  /**
   * Null `Dimension` instance (same as `DEFAULT`).
   *
   * @type {baja.Dimension}
   */
  Dimension.NULL = Dimension.DEFAULT;

  /**
   * Used to maintain an internal cache of `Dimension` objects, keyed by value.
   * 
   * @private
   * @type {Object}
   */
  Dimension.$cache = {};

  /**
   * Create a new instance of `baja.Dimension`. Each parameter will be an
   * exponent for a particular unit of measurement. 0 indicates the absence of
   * that unit, 1 indicates linear, 2 for squared, 3 for cubed, etc.
   * 
   * @param {Number} meter=0 exponent for the meter component
   * @param {Number} kilogram=0 exponent for the kilogram component
   * @param {Number} second=0 exponent for the second component
   * @param {Number} ampere=0 exponent for the ampere component
   * @param {Number} kelvin=0 exponent for the Kelvin component
   * @param {Number} mole=0 exponent for the mole component
   * @param {Number} candela=0 exponent for the candela component
   * @param {Number} dollar=0 exponent for the dollar component
   * @returns {baja.Dimension}
   */
  Dimension.make = function (meter, kilogram, second, ampere,
      kelvin, mole, candela, dollar) {
    
    if (!meter && !kilogram && !second && !ampere && !kelvin && !mole && 
      !candela && !dollar) {
      return baja.Dimension.DEFAULT;
    }
    
    var dim = new Dimension(meter, kilogram, second, ampere, kelvin, mole,
          candela, dollar),
        str = dim.encodeToString(),
        existing = Dimension.$cache[str];
    
    return existing || (Dimension.$cache[str] = dim);
  };

  /**
   * @see baja.Dimension.make
   * @returns {baja.Dimension}
   */
  Dimension.prototype.make = function () {
    return Dimension.make.apply(this, arguments);
  };

  /**
   * Decode a `Dimension` from a `String`.
   * @method
   * @param {String} str
   * @returns {baja.Dimension}
   */
  Dimension.prototype.decodeFromString = cacheDecode(function (str) {
    if (!str) {
      return Dimension.DEFAULT;
    }
    
    var matches = str.match(DECODE_STRING_REGEX),
        map = {},
        match,
        value,
        symbol,
        i;
    
    if (!matches) {
      throw new Error("invalid input '" + str + "'");
    }
    
    for (i = 0; i < matches.length; i++) {
      match = DECODE_SEGMENT_REGEX.exec(matches[i]);
      if (match) {
        symbol = match[1];
        if (SYMBOLS.indexOf(symbol) < 0) {
          throw new Error("invalid symbol '" + symbol + "'");
        }
        value = parseFloat(match[2]) || 1;
        map[symbol] = value;
      }
    }
    
    return baja.Dimension.make(map.m, map.kg, map.s, map.A, map.K, map.Mol, 
      map.cd, map.$);
  });

  /**
   * Encode a `Dimension` to a `String`.
   * 
   * @method
   * @returns {String}
   */
  Dimension.prototype.encodeToString = cacheEncode(function () {
    var that = this,
        values = [ that.$m, that.$kg, that.$s, that.$A,
                   that.$K, that.$mol, that.$cd, that.$$ ],
        value,
        outValues = [],
        outStr,
        i;
    
    for (i = 0; i < values.length; i++) {
      value = values[i];
      if (value) {
        outValues.push(value === 1 ? SYMBOLS[i] : SYMBOLS[i] + value);
      }
    }
    
    outStr = outValues.join(')(');
    
    return outStr && '(' + outStr + ')';
  });

  /**
   * Returns a human-readable representation of the dimension.
   * 
   * @returns {String}
   */
  Dimension.prototype.toString = function () {
    var that = this,
        values = [ that.$m, that.$kg, that.$s, that.$A,
                   that.$K, that.$mol, that.$cd, that.$$ ],
        value,
        outValues = [],
        i;
  
    for (i = 0; i < values.length; i++) {
      value = values[i];
      if (value) {
        outValues.push(SYMBOLS[i] + toExponent(value));
      }
    }

    return outValues.join('·');
  };

  /**
   * Get the meter component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getMeter = function () { return this.$m; };

  /**
   * Get the kilogram component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getKilogram = function () { return this.$kg; };

  /**
   * Get the second component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getSecond = function () { return this.$s; };

  /**
   * Get the ampere component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getAmpere = function () { return this.$A; };

  /**
   * Get the Kelvin component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getKelvin = function () { return this.$K; };

  /**
   * Get the mole component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getMole = function () { return this.$mol; };

  /**
   * Get the candela component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getCandela = function () { return this.$cd; };

  /**
   * Get the dollar component of this dimension.
   * @returns {Number}
   */
  Dimension.prototype.getDollar = function () { return this.$$; };

  return Dimension;
});