baja/obj/Simple.js

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

/**
 * Defines {@link baja.Simple}.
 * 
 * @module baja/obj/Simple
 */
define([ "bajaScript/sys",
        "bajaScript/baja/obj/Value" ], function (
         baja,
         Value) {
  
  'use strict';
  
  var subclass = baja.subclass,
      callSuper = baja.callSuper,
      bajaHasType = baja.hasType;
  
  /**
   * Represents `baja:Simple` in BajaScript.
   * 
   * `Simple`s are immutable and represent primitive data types in Niagara. 
   * They are the basic building blocks of the architecture. Simples contain 
   * no slots themselves but do contain an implicit data value that can be 
   * encoded and decoded in a `String` format.
   * 
   * `Simple`s must be immutable and under no circumstances should there be 
   * any attempt to modify the contents of a Simple.
   * 
   * All `Simple`s must conform to the following conventions... 
   * 
   * * Define a `DEFAULT` instance on the `Simple` constructor.
   * * Define a `make` method.
   * * Define a `decodeFromString` method on the Object's instance that takes a 
   *   `String` and returns an instance of the `Simple`.
   * * Define an `encodeToString` method on the Object's instance that encodes 
   *   the value to a `String`.
   *   
   * Since this Constructor represents an abstract class, it should never
   * be directly used to create a new object.
   *
   * @class
   * @alias baja.Simple
   * @extends baja.Value
   */
  var Simple = function Simple() { 
    callSuper(Simple, this, arguments);
  };
  
  subclass(Simple, Value);
  
  /**
   * Equality test.
   *
   * @param obj
   * @returns {Boolean}
   */
  Simple.prototype.equals = function (obj) {
    // Comparing in via encodeToString is ok because most Simples
    // lazily cache their string encoding (since they're all immutable)    
    return bajaHasType(obj) && 
           obj.getType().equals(this.getType()) && 
           obj.encodeToString() === this.encodeToString();
  };

 /**
  * Returns the String representation of this object.
  *
  * @see baja.Object#toString
  *
  * @param {Object} [cx] optional context information to be used when
  * formatting the string
  *
  * @returns {String|Promise.<String>} a string (if no context passed), or
  * either a string or a Promise (if context passed).
  */
  Simple.prototype.toString = function (cx) {
    return this.encodeToString();
  };

  /**
   * The string encoding of certain Simples may include Type information, or other data that may
   * require asynchronous operations to decode. For instance, some Simples may function as
   * "containers" for other Simples, and may include that Type information in the string encoding.
   * That Simple would then need to import those Types before it could be fully decoded in the
   * browser.
   *
   * baja.Facets is a great example of this. A Facets may contain FrozenEnum values that are
   * defined by Types, such as `baja:Weekday`. For a Facets containing a Weekday to be fully
   * constructed in the browser, the `baja:Weekday` Type must be imported first. Since importing
   * a Type may require a network call, this Facets instance might not be able to be constructed
   * synchronously, using only `decodeFromString()`. baja.Facets has itself implemented
   * `decodeAsync()` to import any necessary Types.
   *
   * When implementing a Type Extension for a Simple, if your Simple references arbitrary Types that
   * need to be imported when decoding your Simple, implement `decodeAsync()` and perform any Type
   * imports (or other asynchronous operations) there.
   *
   * If you are writing code that decodes Simples from strings - that is, when you have a type spec
   * and string encoding, and you need to be able to decode any kind of Simple - favor the use of
   * `decodeAsync`, as it gives the individual Simple a chance to perform async operations to ensure
   * that the decoded Simple is fully correct.
   *
   * The default implementation just returns `decodeFromString` directly.
   *
   * Prior to Niagara 4.14, `decodeAsync()` was only used in field editors. In 4.14 and later,
   * `decodeAsync()` will be used by BajaScript itself to support asynchronous decoding of Simples
   * when resolving ORDs and retrieving other data from the station.
   *
   * (Note: `decodeAsync()` cannot be used by the framework to decode frozen slots. If you
   * have a frozen slot, whose definition is a Simple that would *require* the use of
   * `decodeAsync()` to construct it, it will not work. The best approach would be to implement a
   * Type Extension that would use the `baja!` plugin to preload all the types referenced by the
   * default value of that frozen slot, so that `decodeFromString` would have all the information
   * it needed to construct it synchronously.)
   *
   * @param {string} str
   * @param {baja.comm.Batch} [batch] optional batch to use
   * @returns {baja.Simple|Promise.<baja.Simple>} may return the Simple instance
   * directly, or a Promise resolving to same - so wrap in `Promise.resolve()`
   * if unsure.
   * @example
   * return Promise.resolve(baja.$(simpleTypeSpec).decodeAsync(stringEncoding))
   *   .then((simpleInstance) => {});
   */
  Simple.prototype.decodeAsync = function (str, batch) {
    return this.decodeFromString(str);
  };

  /**
   * @returns {String} the string encoding of the Simple, by default
   */
  Simple.prototype.valueOf = function () {
    return this.encodeToString();
  };

  /**
   * This flag is intended for internal use to be passed to decodeFromString in order to indicate
   * that the synchronous decode is okay for simples that have a precondition such as all types
   * are loaded.
   * @private
   */
  Simple.$unsafeDecode = { unsafe: true };

  /**
   * Every Simple implementation must have an `encodeToString` function.
   * 
   * @function 
   * @name baja.Simple#encodeToString
   * @abstract
   * @returns {string}
   */

  /**
   * Every Simple implementation must have a `decodeFromString` function.
   *
   * @function
   * @name baja.Simple#decodeFromString
   * @abstract
   * @param {string} str
   * @returns {baja.Simple}
   */

  /**
   * Every Simple implementation must have a `make` function. It can take
   * any arbitrary arguments. When constructing the Simple with `baja.$`, any
   * additional arguments will be passed to `make()`, with the following special
   * cases:
   *
   * - When _no_ arguments are passed, `baja.$` will return the `DEFAULT`
   *   instance.
   * - When _one_ argument is passed and it is a string, `baja.$` will use
   *   `decodeFromString()` to create the instance.
   *
   * @function
   * @name baja.Simple#make
   * @abstract
   * @returns {baja.Simple}
   * @example
   * //in the Simple declaration:
   * MySimple.prototype.make = function (a, b, c) {
   *   var mySimple = new MySimple();
   *   mySimple.a = a;
   *   mySimple.b = b;
   *   mySimple.c = c;
   *   return mySimple;
   * };
   * 
   * //when constructing:
   * var mySimple = baja.$('myModule:MySimple', 'a', 'b', 'c');
   */

  return Simple;
});