Source: src/main/javascript/oracle/oj/ojcore/Object.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/*
** Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
**
**34567890123456789012345678901234567890123456789012345678901234567890123456789
*/
/*jslint browser: true*/
/*global define: false,goog: true*/

/**
 * Base class of all OJET Objects.
 * <p>
 * To create a subclass of another oj.Object, use oj.Object.createSubclass.
 * The subclass can specify class-level initialization by implementing an
 * <code>InitClass()</code> method on its constructor.  <code>InitClass</code>
 * is guaranteed to be called only once per class.  Further, a class'
 * <code>InitClass</code> method is guranteed to be called only after its
 * superclass' class initialization has been called.  When <code>InitClass</code>
 * is called, <code>this</code> is the class' constructor.  This allows class
 * initialization implementations to be shared in some cases.
 * </p>
 * @author Blake Sullivan
 */


/**
 * @constructor
 * @export
 */
oj.Object = function()
{
  this.Init();
};
 
oj.Object.superclass = null;

/**
 * @private
 */
oj.Object._typeName = "oj.Object";

// regular expressicloneon for stripping out the name of a function
/**
 * @private
 */
oj.Object._GET_FUNCTION_NAME_REGEXP = /function\s+([\w\$][\w\$\d]*)\s*\(/;
// oj.Object._TRIM_REGEXP = /(^\s*)|(\s*$)/g; this.replace(/(^\s*)|(\s*$)/g, "");

oj.Object.prototype = {};
oj.Object.prototype.constructor = oj.Object;


/**
 * Calls to  this method are added by the Closure Compiler pass during JET's build process.
 * It should never be called by the Application code
 * 
 * The method delegates to goog.exportProperty() for exporting a symbol with Closure compiler,
 * while recording a map of the renamed names to an original names and a map of original names to the renamed names
 * @param {string} name - name of the property ('CCCC.prototype.FFFF' is expected)
 * @param {Object} valueMapping - a name-value pair, where tke key is the renamed name (renamed FFFF), and the value is the refernce to the member function
 * whose name was exported
 * @ignore
 */
oj.Object.exportPrototypeSymbol = function(name, valueMapping)
{
  var renamed = null;
  var val = null, prop;
  for (prop in valueMapping)
  {
    if (valueMapping.hasOwnProperty(prop)) {
        renamed = prop;
        val = valueMapping[prop];
        break;
    }
  }

  var tokens = name.split('.');

  var constructor = oj[tokens[0]];
  var original = tokens[2];

  // Do nothing if we are exporting a function that has not been renamed
  if (renamed == original || renamed == null)
  {
    return;
  }

  var renameMap = constructor._r2o;
  if (!renameMap)
  {
     renameMap = {};
     constructor._r2o = renameMap;
  }

  renameMap[renamed] = original;

  goog.exportProperty(constructor.prototype, original, val);
};

/**
 * Creates a subclass of a baseClass
 * @param {Function} extendingClass The class to extend from the base class
 * @param {Function} baseClass class to make the superclass of extendingClass
 * @param {string=} typeName to use for new class.  If not specified, the typeName will be extracted from the
 * baseClass's function if possible
 * @export
 */
oj.Object.createSubclass = function(
  extendingClass,
  baseClass,
  typeName)  // optional name to name this class
{
  oj.Assert.assertFunction(extendingClass);
  oj.Assert.assertFunctionOrNull(baseClass);
  oj.Assert.assertStringOrNull(typeName);

  if (baseClass === undefined)
  {
    // assume oj.Object
    baseClass = oj.Object;
  }

  oj.Assert.assert(extendingClass !== baseClass, "Class can't extend itself");

  // use a temporary constructor to get our superclass as our prototype
  // without out having to initialize the superclass
  /**
   * @private
   * @constructor
   */
  var TempConstructor = oj.Object._tempSubclassConstructor;

  TempConstructor.prototype = baseClass.prototype;
  extendingClass.prototype = new TempConstructor();

  extendingClass.prototype.constructor = extendingClass;
  extendingClass.superclass = extendingClass["superclass"] = baseClass.prototype;

  if (typeName) {
    extendingClass._typeName = typeName;
  }
};

/**
 * Copies properties from the source object to the prototype of the target class
 * Only properties 'owned' by the source object will be copied, i.e. the properties
 * from the source object's prototype chain will not be included.
 * To copy properties from another class with methods defined on the prototype, pass
 * otherClass.prototype as the source.
 * @param {Function} targetClass - the function whose prototype will be used a 
 * copy target
 * @param {Object} source - object whose properties will be copied
 * @export
 */
oj.Object.copyPropertiesForClass = function(targetClass, source) 
{ 
  var prop;
  oj.Assert.assertFunction(targetClass);
  oj.Assert.assert(source != null, "source object cannot be null");
  
  for(prop in source) 
  { 
    if(source.hasOwnProperty(prop)) 
    { 
      targetClass.prototype[prop] = source[prop]; 
    } 
  } 
};

/**
 * @private
 */
oj.Object._tempSubclassConstructor = function(){};




/**
 * Returns the class object for the instance
 * @param {Object=} otherInstance - if specified, the instance whose type
 * should be returned. Otherwise the type if this instance will be returned
 * @return {Function} the class object for the instance
 * @final
 * @export
 */
oj.Object.prototype.getClass = function(
  otherInstance)
{
  if (otherInstance === undefined) {
    otherInstance = this;
  }
  else if (otherInstance === null)
  {
    return null;
  }
  return otherInstance["constructor"];
};


/**
 * Returns a clone of this object.  The default implementation is a shallow
 * copy.  Subclassers can override this method to implement a deep copy.
 * @return {Object} a clone of this object
 * @export
 */
oj.Object.prototype.clone = function()
{
  var clone = new this.constructor();

  oj.CollectionUtils.copyInto(clone, this);

  return clone;
};

/**
 * @export
 */
oj.Object.prototype.toString = function()
{
  return this.toDebugString();
};

/**
 * @export
 */
oj.Object.prototype.toDebugString = function()
{
  return this.getTypeName() + " Object";
};


/**
 * Returns the type name for a class derived from oj.Object
 * @param {Function!|null} clazz Class to get the name of
 * @return {String} name of the Class
 * @export
 */
oj.Object.getTypeName = function(clazz)
{
  oj.Assert.assertFunction(clazz);

  var typeName = clazz._typeName, constructorText, matches;

  if (typeName == null)
  {
    constructorText = clazz.toString();
    matches = oj.Object._GET_FUNCTION_NAME_REGEXP.exec(constructorText);

    if (matches)
    {
      typeName = matches[1];
    }
    else
    {
      typeName = "anonymous";
    }

    // cache the result on the function
    clazz._typeName = typeName;
  }

  return typeName;
};

/**
 * Returns the type name for this instance
 * @return {String} name of the Class
 * @final
 * @export
 */
oj.Object.prototype.getTypeName = function()
{
  return oj.Object.getTypeName(this.constructor);
};

/**
 * Initializes the instance.  Subclasses of oj.Object must call
 * their superclass' Init
 * @export
 */
oj.Object.prototype.Init = function()
{
  if (oj.Assert.isDebug()) {
    oj.Assert.assert(this["getTypeName"], "Not an oj.Object");
  }

  // do any class initialization.  This code is duplicated from
  // oj.Object.ensureClassInitialization()

  var currClass = this.constructor;
  if (!currClass._initialized) {
    oj.Object._initClasses(currClass);
  }
};

/**
 * Ensures that a class is initialized.  Although class initialization occurs
 * by default the first time that an instance of a class is created, classes that
 * use static factory methods to create their instances may
 * still need to ensure that their class has been initialized when the factory
 * method is called.
 *
 * @param {Function} clazz The class to ensure initialization of
 * @export
 */
oj.Object.ensureClassInitialization = function(clazz)
{
  oj.Assert.assertFunction(clazz);

  if (!clazz._initialized) {
    oj.Object._initClasses(clazz);
  }
};


/**
 * Indicates whether some other oj.Object is "equal to" this one.
 * Method is equivalent to java ".equals()" method.
 * @param {Object} object - comparison target
 * @return {boolean} true if if the comparison target is equal to this object, false otherwise
 * @export
 */
oj.Object.prototype.equals = function(
  object)
{
  return this === object;
};

/**
 * Binds the supplied callback function to an object
 * @param {Object!} obj - object that will be available to the supplied callback 
 * function as 'this'
 * @param {Function!} func - the original callback
 * @return {Function} a function that will be invoking the original callback with
 * 'this' object assigned to obj
 * @export
 */
oj.Object.createCallback = function(obj, func)
{
  oj.Assert.assertFunction(func);

  // All browsers supported by JET support bind() method
  return func.bind(obj);
};


/**
 * @private
 */
oj.Object._initClasses = function(currClass)
{
  if (oj.Assert.isDebug())
  {
    oj.Assert.assertFunction(currClass);
    oj.Assert.assert(!currClass._initialized);
  }

  currClass._initialized = true;

  var superclass = currClass.superclass, superclassConstructor, typeName, InitClassFunc;

  // initialize the superclass if necessary
  if (superclass)
  {
    superclassConstructor = superclass.constructor;

    if (superclassConstructor && !superclassConstructor._initialized) {
      oj.Object._initClasses(superclassConstructor);
    }
      
    oj.Object._applyRenamesToSubclass(currClass);
  }

  
  // if the class has an initialization function, call it
  InitClassFunc = currClass["InitClass"] || null;
  
  // Check for the quoted name in case InitClass is renamed by Closure compiler
  if (!InitClassFunc)
  {
    InitClassFunc = currClass["InitClass"];
  }

  if (InitClassFunc)
  {
    InitClassFunc.call(currClass);
  }
};

/**
 * Compares 2 values using strict equality except for the case of 
 * <ol>
 *   <li> Array [order matters]; will traverse through the arrays and compare oj.Object.compareValues(array[i], array2[i]) </li>
 *   <li> Instances that support valueOf [i.e. Boolean, String, Number, Date, and etc] will be compared by usage of that function </li>
 * </ol>
 * 
 * @public
 * @export
 */
oj.Object.compareValues = function (obj1, obj2)
{
  if (obj1 === obj2) 
  {
    return true;
  }
  
  var obj1Type = typeof obj1,
      obj2Type = typeof obj2;
  
  if (obj1Type !== obj2Type) 
  {
    //of different type so consider them unequal
    return false;
  }
  
  //At this point means the types are equal
  
  //note that if the operand is an array or a null then typeof is an object
  //check if either is null and if so return false [i.e. case where one might be a null and another an object]
  //and one wishes to avoid the null pointer in the following checks. Note that null === null has been already tested 
  if(obj1 === null || obj2 === null) 
  {
    return false;
  }
  
  //now check for constructor since I think by here one has ruled out primitive values and if the constructors 
  //aren't equal then return false
  if(obj1.constructor === obj2.constructor) 
  {
    
    //these are special cases and will need to be modded on a need to have basis
    if(Array.isArray(obj1))
    {
      return oj.Object._compareArrayValues(obj1, obj2);
    }
    else if(obj1.constructor === Object)
    {
      //for now invoke innerEquals and in the future if there are issues then resolve them
      return oj.Object.__innerEquals(obj1, obj2);
    }
    else if(obj1["valueOf"] && typeof obj1["valueOf"] === "function") 
    {
      //test cases for Boolean, String, Number, Date
      //Note if some future JavaScript constructors 
      //do not impl it then it's their fault
      return obj1.valueOf() === obj2.valueOf();
    }
    
  }
  
  return false;
};

oj.Object._compareArrayValues = function (array1, array2) 
{
  if (array1.length !== array2.length)
  {
    return false;
  }

  for (var i = 0, j = array1.length;i < j;i++)
  {
    //recurse on each of the values, order does matter for our case since do not wish to search 
    //for the value [expensive]
    if (!oj.Object.compareValues(array1[i], array2[i]))
    {
      return false;
    }
  }
  return true;
}

//Shadow comparion of two arrays, order needn't be same, but no duplicates
oj.Object._compareSet = function (array1, array2) 
{
  //null and [] are equals
  if (!array1)
    return (!array2 || array2.length == 0);

  if (!array2)
    return (!array1 || array1.length == 0);

  if (array1.length != array2.length)
    return false;

  for (var i = 0; i < array1.length; i++)
  {
    if ((array1[i] != array2[i]) &&
        ((array1.indexOf(array2[i]) == -1 ||
          array2.indexOf(array1[i]) == -1)))
      return false;
  }

  return true;
}


oj.Object.__innerEquals = function (obj1, obj2) {
  var prop, hasProperties = false;

  if (obj1 === obj2) {
    return true;
  }

  if (!(obj1 instanceof Object) || !(obj2 instanceof Object)) {
    return false;
  }

  if (obj1.constructor !== obj2.constructor) 
  {
    return false;
  }

  for (prop in obj1) 
  {
    if (!hasProperties)
    {
      hasProperties = true;
    }
    if (obj1.hasOwnProperty(prop)) {        
      if (!obj2.hasOwnProperty(prop)) 
      {
        return false;
      }

      if (obj1[prop] !== obj2[prop]) 
      {
        if (typeof(obj1[prop]) !== 'object') {
          return false;
        }

        if (!oj.Object.__innerEquals(obj1[prop], obj2[prop])) 
        {
          return false;
        }
      }
    }
  }
  
  for (prop in obj2) 
  {
    if (!hasProperties)
    {
      hasProperties = true;
    }
    
    if (obj2.hasOwnProperty(prop) && !obj1.hasOwnProperty(prop)) {
      return false;
    }
  }
  
  if (!hasProperties)
  {
    // we are dealing with objects that have no properties like Number or Date.
    return JSON.stringify(obj1) === JSON.stringify(obj2);
  }
  
  return true;
};

oj.Object.isEmpty = function(object) {
    var prop;
    // Test if an object is empty
    if (object === undefined || object === null) {
        return true;
    }
    
    for (prop in object) {
        if (object.hasOwnProperty(prop)) {
            return false;
        }
    }
    return true;
};

/**
 * @private
 */
oj.Object._applyRenamesToSubclass = function (currClass)
{
  // Check whether any renames actually happened
  if (!oj.Object._r2o)
  {
    return;
  }
  var ancestor = currClass.superclass;
  oj.Object._applyRenamesFromChain(currClass, ancestor);
};

/**
 * @private
 */
oj.Object._applyRenamesFromChain = function(currClass, superclass)
{
  if (!superclass)
  {
    return;
  }
  
  var ancestor = superclass.constructor;
  
  
  //Recurse up the inheritance chain first
  oj.Object._applyRenamesFromChain(currClass, ancestor.superclass);
  
  var renameMap = ancestor._r2o, alias;
  if (renameMap)
  {
    for (alias in renameMap)
    {
      if (renameMap.hasOwnProperty(alias)) {
        var orig = renameMap[alias];
        if (alias != orig)
        {
          var prot = currClass.prototype;
          if (!prot.hasOwnProperty(alias) && prot.hasOwnProperty(orig))
          {
            prot[alias] = prot[orig];
          }
          else if(!prot.hasOwnProperty(orig) && prot.hasOwnProperty(alias))
          {
            prot[orig] = prot[alias];
          }
        }
      }  
    }
  }
};

/**
 * @private
 * @return  {boolean} true if AMD Loader (such as Require.js) is present, 
 *                    false otherwise
 */
oj.__isAmdLoaderPresent = function()
{
  return (typeof define === 'function' && define['amd']);
};