SOURCE CODE: Uize.Util.Oop (view docs)

/*______________
|       ______  |   U I Z E    J A V A S C R I P T    F R A M E W O R K
|     /      /  |   ---------------------------------------------------
|    /    O /   |    MODULE : Uize.Util.Oop Package
|   /    / /    |
|  /    / /  /| |    ONLINE : http://www.uize.com
| /____/ /__/_| | COPYRIGHT : (c)2010-2016 UIZE
|          /___ |   LICENSE : Available under MIT License or GNU General Public License
|_______________|             http://www.uize.com/license.html
*/

/* Module Meta Data
  type: Package
  importance: 7
  codeCompleteness: 80
  docCompleteness: 10
*/

/*?
  Introduction
    The =Uize.Util.Oop= package provides convenience methods for...

    *DEVELOPERS:* `Chris van Rensburg`
*/

Uize.module ({
  name:'Uize.Util.Oop',
  builder:function () {
    'use strict';

    var
      /*** Variables for Scruncher Optimization ***/
        _undefined,
        _isFunction = Uize.isFunction,

      /*** General Variables ***/
        _package,
        _sacredEmptyArray = [],

        /*** used by the Uize.Util.Oop.isPackage method ***/
          _typicalPackageFunction = Uize.package (),
          _typicalPackageFunctionPrototype = _typicalPackageFunction.prototype,
          _typicalPackageFunctionToString = _typicalPackageFunction.toString,
          _typicalPackageFunctionAsString = _typicalPackageFunction.toString ()
    ;

    /*** Utility Functions ***/
      function _isClass (_object) {
        return (
          _object != _undefined &&
          (
            _isFunction (_object) ||
            (
              typeof _object == 'object' && !_object.constructor
              /* NOTE:
                In Internet Explorer, certain DOM object "classes" like HTMLBodyElement are reported as object, rather than function, and the value of their constructor property is undefined. Normally, the value of the constructor property for a non-null object is the constructor function for that object. In the case of the document.body element in IE, the constructor is a reference to HTMLBodyElement. That's all fine and good, but HTMLBodyElement is not a function. Instead, it's an object, whose constructor is undefined. We use this as an indication that the object is really a "class", until a better test is worked out.
              */
            )
          )
        );
      }

    return _package = Uize.package ({
      getFeatures:function (_instanceOrClass) {
        var
          _class = _package.resolveToClass (_instanceOrClass),
          _features = []
        ;

        if (_class) {
          var _getFeaturesForContext = function (_contextName) {
            var
              _contextIsSetGet = _contextName == 'state',
              _context = _contextIsSetGet
                ? _class.get ()
                : _contextName == 'instance' ? _class.prototype : _class,
              _featureNamePrefix = _contextName == 'static' ? _package.getClassName (_class) + '.' : '',
              _contextNameCapped = Uize.capFirstChar (_contextName),
              _propertyValue,
              _featureIsPublic
            ;
            for (var _propertyName in _context)
              (_featureIsPublic = _propertyName.indexOf ('_') < 0) &&
                _features.push (
                  Uize.copyInto (
                    _package.getFeatureInfo (_class,_contextName,_propertyName),
                    {
                      name:_featureNamePrefix + _propertyName,
                      shortName:_propertyName,
                      access:_featureIsPublic ? 'Public' : 'Private',
                      context:_contextNameCapped,
                      type:_contextIsSetGet || !_isFunction (_propertyValue = _context [_propertyName])
                        ? 'Property'
                        : typeof _propertyValue.moduleName == 'string'
                          ? 'Module'
                          : 'Method'
                    }
                  )
                )
            ;
          };

          _getFeaturesForContext ('instance');
          _getFeaturesForContext ('static');
          _package.isUizeClass (_class) && _getFeaturesForContext ('state');

          /*** sort the features by the keys: access, context, type, name ***/
            var _compareTwo = function (_valueA,_valueB) {
              return _valueA < _valueB ? -1 : _valueA > _valueB ? 1 : 0;
            };
            _features.sort (
              function (_featureA,_featureB) {
                return (
                  _compareTwo (_featureA.access,_featureB.access) ||
                  _compareTwo (_featureA.context,_featureB.context) ||
                  _compareTwo (_featureA.type,_featureB.type) ||
                  _compareTwo (_featureA.name,_featureB.name)
                );
              }
            );

        }

        return _features;
        /*?
          Static Methods
            Uize.Util.Oop.getFeatures
              Returns an array, representing all of the features that can be automatically detected from the specified class (or the class of the specified instance).

              SYNTAX
              ...............................................................
              featuresARRAY = Uize.Util.Oop.getFeatures (instanceOrClassOBJ);
              ...............................................................

              Each element of the array returned by the =Uize.Util.Oop.getFeatures= method is an object that describes an individual feature, as would be returned by the related =Uize.Util.Oop.getFeatureInfo= static method.

              NOTES
              - see the related =Uize.Util.Oop.getFeatureInfo= static method
        */
      },

      inheritsFrom:function (_testInstanceOrClass,_baseInstanceOrClass) {
        if (_baseInstanceOrClass == _undefined) return _testInstanceOrClass == _undefined;
        if (_testInstanceOrClass == _undefined) return _baseInstanceOrClass == Object;
        var
          _testClass =
            _isClass (_testInstanceOrClass) ? _testInstanceOrClass : _testInstanceOrClass.constructor,
          _baseClass =
            _isClass (_baseInstanceOrClass) ? _baseInstanceOrClass : _baseInstanceOrClass.constructor
        ;
        while (_testClass != _baseClass && _isFunction (_testClass = _testClass.superclass));
        return _testClass == _baseClass;
      },

      isClass:_isClass,

      isPackage:function (_object) {
        if (
          !_isClass (_object) ||
            // if object is not a function, then we don't consider it a package
          _typicalPackageFunctionToString.call (_object) != _typicalPackageFunctionAsString
            // if function has any construction code, then we don't consider it a package
        )
          return false
        ;

        /*** check to see if function's prototype differs from typical package function's prototype ***/
          var _objectPrototype = _object.prototype;
          for (var _propertyName in _objectPrototype)
            if (
              _objectPrototype [_propertyName] !== _typicalPackageFunctionPrototype [_propertyName] ||
              !(_propertyName in _typicalPackageFunctionPrototype)
            )
              return false
          ;

        return true;
      },

      isUizeClass:function (_object) {
        /* NOTE:
          We employ duck-typing here because we want this method to also work for object references that come from different windows, so we can't rely on instanceof or similar approaches.
        */
        return (
          _isClass (_object) &&
          _isFunction (_object.subclass) &&
          _isFunction (_object.superclass) &&
          _isFunction (_object.set) &&
          _isFunction (_object.get)
        );
      },

      isUizeClassInstance:function (_value) {
        return Uize.isObject (_value) && _package.isUizeClass (_value.constructor);
      },

      getClassName:function (_class) {
        return (
          (_class != _undefined && _class.moduleName) ||
          (
            (
              (_class + '').match (
                typeof _class == 'object'
                  ? /\[object\s+([^\]]+)\]/
                  : /^\s*function\s+([^\(]+)\s*\(/
              ) || _sacredEmptyArray
            ) [1]
          ) || ''
        );
      },

      getFeatureInfo:function (_class,_contextName,_featureName) {
        function _returnClass () {return _class}
        function _returnPrototype () {return _class.prototype}
        function _returnSetGetProperties () {return _class.get ()}
        var
          _isInstanceFeature = _contextName == 'instance',
          _isSetGetFeature = !_isInstanceFeature && _contextName == 'state',
          _getFeatureContext = _isInstanceFeature
            ? _returnPrototype
            : _isSetGetFeature ? _returnSetGetProperties : _returnClass,
          _feature = _getFeatureContext () [_featureName],
          _featureContext,
          _introducedIn = _class,
          _overriddenIn = _class
        ;
        while (
          (_class = _class.superclass) &&
          _class.superclass && // ignore the stub superclass of the Uize base class
          (_featureContext = _getFeatureContext ()) &&
          _featureName in _featureContext
        ) {
          if (_introducedIn == _overriddenIn && _featureContext [_featureName] == _feature)
            _overriddenIn = _class
          ;
          _introducedIn = _class;
        }
        return {introducedIn:_introducedIn,overriddenIn:_overriddenIn};
        /*?
          Static Methods
            Uize.Util.Oop.getFeatureInfo

              SYNTAX
              ...................................................................................
              featureInfoOBJ = Uize.Util.Oop.getFeatureInfo (classOBJ,contextSTR,featureNameSTR);
              ...................................................................................

              FEATURE INFO OBJECT
              .........................
              name      : nameSTR,
              shortName : shortNameSTR,
              access    : accessSTR,
              context   : contextSTR,
              type      : typeSTR
              .........................

              Properties of the feature object...

              - *name* -
              - *shortName* -
              - *access* -
              - *context* -
              - *type* -

              NOTES
              - see the related =Uize.Util.Oop.getFeatures= static method
        */
      },

      getInheritanceChain:function (_class) {
        var _inheritanceChain = [];
        if (_package.isUizeClass (_class)) {
          var _superclass = _class;
          while (_superclass.moduleName) {
            _inheritanceChain.unshift (_superclass);
            _superclass = _superclass.superclass;
          }
        }
        return _inheritanceChain;
      },

      resolveToClass:function (_instanceOrClass) {
        return (
          _instanceOrClass == _undefined || _isFunction (_instanceOrClass)
            ? _instanceOrClass || _undefined
            : _instanceOrClass.constructor
        );
      }
    });
  }
});