baja/obj/NameMap.js

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

/**
 * Defines {@link baja.NameMap}
 * @module baja/obj/NameMap
 */
define([
  "bajaScript/sys",
  "bajaScript/baja/obj/Simple",
  "bajaScript/baja/obj/objUtil" ], function (
  baja,
  Simple,
  objUtil) {
  
  "use strict";
  
  var subclass = baja.subclass,
      callSuper = baja.callSuper,
      strictArg = baja.strictArg,
      
      cacheDecode = objUtil.cacheDecode,
      cacheEncode = objUtil.cacheEncode;
  
  /**
   * `NameMap` used for managing a list of `String` names to `Format` values.
   * 
   * This Constructor shouldn't be invoked directly. Please use the `make()` 
   * methods to create an instance of a `NameMap` Object. 
   *
   * @class
   * @alias baja.NameMap
   * @extends baja.Simple
   */
  var NameMap = function NameMap(map, $fromDecode) {
    callSuper(NameMap, this, arguments); 
    
    if ($fromDecode) {
      this.$map = map;
    } else {
      strictArg(map, Object);
      this.$map = {};
      // Copy over Properties into this map
      var p,
          v;
      for (p in map) {
        if (map.hasOwnProperty(p)) {
          v = map[p];
          this.$map[p] = v instanceof baja.Format ? v : baja.Format.make(v);
        }
      }
    }
  };
  
  subclass(NameMap, Simple);
  
  /**
   * `NameMap` default instance
   * @type {baja.NameMap}
   */
  NameMap.DEFAULT = new NameMap({});
  
  /**
   * Make a `NameMap` object.
   *
   * @param {Object} map an object containing key/value pairs.
   * @returns {baja.NameMap}
   */
  NameMap.prototype.make = function (map, $fromDecode) {
    if (!map) {
      return NameMap.DEFAULT;
    }
    return new NameMap(map, $fromDecode);
  };
  
  /**
   * Make a `NameMap` object.
   *
   * @param {Object} map an object containing key/value pairs.
   * @returns {baja.NameMap}
   */
  NameMap.make = function (map) {
    return NameMap.DEFAULT.make.apply(NameMap.DEFAULT, arguments);
  };
  
  /**
   * Decode `NameMap` from a `String`.
   *
   * @method
   * @returns {baja.NameMap}
   */  
  NameMap.prototype.decodeFromString = cacheDecode(function (s) {
    if (s === "{}") {
      return NameMap.DEFAULT;
    }
    
    // Parse everything between {...}
    var res = /^{(.*)}$/.exec(s),
        map,
        i = 0,
        buf = "",
        lastDelim = ";",
        key,
        c,
        body;
    
    if (!res) {
      throw new Error("Invalid NameMap");
    }
    
    if (!res[1]) {
      return NameMap.DEFAULT;
    }
    
    // Parse each key value pair (key=value;)
    map = {};
    body = res[1];
    
    // Due to the escaping, this is very difficult to do with regular expressions so we're just
    // going to do this the old fashioned way.
    for (i = 0; i < body.length; ++i) {
      c = body.charAt(i);
      
      if (c === "\\") {
        buf += body.charAt(++i);
      } else if (c !== "=" && c !== ";") {
        buf += c;
      } else {
        if (c === lastDelim) {
          throw new Error("Invalid NameMap Encoding");
        }
        lastDelim = c;
        if (!key) {
          key = buf;
        } else {
          map[key] = baja.Format.make(buf);
          key = undefined;
        }
        buf = "";
      }      
    }
    
    return this.make(map, /*$fromDecode*/true);
  });
  
  function escapeNameMapReplace(match) {
    return "\\" + match;
  }
  
  function escapeNameMapValue(val) {
    return val.replace(/[=\\{};]/g, escapeNameMapReplace);
  }
  
  /**
   * Encode `NameMap` to a `String`.
   *
   * @method
   * @returns {String}
   */  
  NameMap.prototype.encodeToString = cacheEncode(function () {
    var s = "{",
        p,
        map = this.$map;
        
    for (p in map) {
      if (map.hasOwnProperty(p)) {
        s += escapeNameMapValue(p);
        s += "=";       
        s += escapeNameMapValue(map[p].encodeToString());
        s += ";";        
      }
    }
    
    s += "}";
    
    return s;
  });
  
  /**
   * Return a `String` representation of the `NameMap`.
   *
   * @returns {String}
   */
  NameMap.prototype.toString = function () {
    return this.encodeToString();
  };
  
  /**
   * Return a `Format` from the Map or null if an entry can't be found.
   *
   * @returns {baja.Format|null} or null if an entry can't be found.
   */
  NameMap.prototype.get = function (name) {
    return this.$map[name] || null;
  };
  
  /**
   * Return a list of all the keys in the Map.
   *
   * @returns {string[]} an array of String key names.
   */
  NameMap.prototype.list = function () {
    return Object.keys(this.$map);
  };

  /**
   * @returns {Object.<string, baja.Format>} an object literal, where the keys
   * are the NameMap keys and the values are the corresponding formats.
   * @since Niagara 4.11
   */
  NameMap.prototype.toObject = function () {
    return Object.assign({}, this.$map);
  };

  /**
   * @returns {boolean} true if the NameMap is empty
   * @since Niagara 4.15
   */
  NameMap.prototype.isNull = function () {
    return !this.list().length;
  };
   
  return NameMap;
});