Source: Hookable/Impl.js

define('Hookable/impl',[
  './namespace','./Get',
  './Raw','./Status','./Hooks', './Descriptor', './Undefined', './internal',
  './alias'
],function (Hookable,Get,
            Raw,Status,Hooks, Descriptor, Undefined, internal) {

  /**
   * @name Undefined
   * @memberOf BeautifulProperties.Hookable
   */
  Hookable.Undefined = Undefined;

  /**
   * @callback BeautifulProperties.Hookable~beforeGet
   */
  /**
   * @callback BeautifulProperties.Hookable~afterGet
   * @param {*} val
   * @param {*} previousVal
   * @return {*} replacedVal
   */
  /**
   * @callback BeautifulProperties.Hookable~beforeSet
   * @param {*} val
   * @param {*} previousVal
   * @return {*} replacedVal
   */
  /**
   * @callback BeautifulProperties.Hookable~afterSet
   * @param {*} val
   * @param {*} previousVal
   */
  /**
   * @callback BeautifulProperties.Hookable~refresh
   * @param {*} val
   * @param {*} previousVal
   */
  /**
   * @function addHook
   * @memberOf BeautifulProperties.Hookable
   *
   * @param {object} object
   * @param {string} key
   * @param {string} hookType beforeGet afterGet beforeSet afterSet refresh
   * @param {BeautifulProperties.Hookable~beforeGet|BeautifulProperties.Hookable~afterGet|BeautifulProperties.Hookable~beforeSet|BeautifulProperties.Hookable~afterSet|BeautifulProperties.Hookable~refresh} hook
   * @param {number=} priority 1..10000.<br/>Default value is 100.
   * @description Add the given hook to the property.<br/>
   * The order of executing hooks:Higher priority -> Lower priprity,Added earlier -> Added later.<br/>
   * afterGet hook could replace get value.<br/>
   * beforeSet hook could replace set value.<br/>
   */
  Hookable.addHook = function addHook(object,key,hookType,hook,priority){
    if (!Hooks.has(object,key)) {
      throw new TypeError('The property (key:'+key+') is not a Hookable property. Hookable.addHook is the method for a Hookable property.');
    }
    var hooks = Hooks.retrieve(object,key);
    priority = priority || 100;
    hooks[hookType].add(hook,priority);
  };
  /**
   * @function define
   * @memberOf BeautifulProperties.Hookable
   *
   * @param {object} object
   * @param {string} key
   * @param {(BeautifulProperties~DataDescriptor|BeautifulProperties~AccessorDescriptor|BeautifulProperties~GenericDescriptor)=} descriptor
   *  descriptor.writable's default value is false in ES5,but it's true in BeautifulProperties.Hookable.
   */
  Hookable.define = function defineHookableProperty(object,key,descriptor) {
    descriptor = descriptor || Object.create(null);
    var type = Descriptor.getTypeOf(descriptor);
    if (type === Descriptor.Types.InvalidDescriptor) {
      throw Descriptor.createTypeError(descriptor);
    }

    var storeDescriptor = Descriptor.store.bind(null,object,key);
    var storedDescriptor = Descriptor.retrieve(object,key);
    if (storedDescriptor) {
      // no change
      if (Descriptor.equals(descriptor,storedDescriptor)) {
        return;
      }
      var storedDescriptorType = Descriptor.getTypeOf(storedDescriptor);
      if (!storedDescriptor.configurable) {
        var isModified = (function (descriptor) {
          // only for data property.
          if (storedDescriptorType === Descriptor.Types.AccessorDescriptor) {
            return false;
          }
          if (!storedDescriptor.writable) {
            return false;
          }
          if (type !== Descriptor.Types.GenericDescriptor && type !== storedDescriptorType) {
            return false;
          }
          descriptor = Descriptor.applyDefault(storedDescriptorType,descriptor,storedDescriptor);
          // except writable&value
          var keys = 'configurable enumerable init'.split(' ');
          for (var i = 0; i < keys.length; i++) {
            if (descriptor[keys[i]] !== storedDescriptor[keys[i]]) {
              return false;
            }
          }
          // store the overrided descriptor
          storeDescriptor(descriptor);
          return true;
        })(descriptor);
        if (isModified) {
          return;
        } else {
          throw new TypeError('Cannot redefine property: ' + key);
        }
      }
      // configurable:true
      if (type === Descriptor.Types.GenericDescriptor || type === storedDescriptorType) {
        // generic or same type
        (function (descriptor) {
          var genericDescriptor;
          if (type === Descriptor.Types.GenericDescriptor) {
            genericDescriptor = descriptor;
          } else {
            genericDescriptor = Object.create(null);
            "configurable enumerable".split(' ').forEach(function (key) {
              if (descriptor[key] !== undefined) {
                genericDescriptor[key] = descriptor[key];
              }
            });
          }
          Object.defineProperty(object,key,genericDescriptor);
        })(descriptor);
        (function (descriptor) {
          descriptor = Descriptor.applyDefault(storedDescriptorType,descriptor,storedDescriptor);
          // store the overrided descriptor
          storeDescriptor(descriptor);
        })(descriptor);
        return;
      } else {
        // different type
        (function (descriptor) {
          var genericDescriptor = Object.create(null);
          "configurable enumerable".split(' ').forEach(function (key) {
            if (descriptor[key] !== undefined) {
              genericDescriptor[key] = descriptor[key];
            }
          });
          Object.defineProperty(object,key,genericDescriptor);
        })(descriptor);
        (function (descriptor) {
          var genericDescriptor = Object.create(null);
          "configurable enumerable".split(' ').forEach(function (key) {
            if (descriptor[key] !== undefined) {
              genericDescriptor[key] = storedDescriptorType[key];
            }
            descriptor = Descriptor.applyDefault(type,descriptor,genericDescriptor);
            // store the overrided descriptor
            storeDescriptor(descriptor);
          });
        })(descriptor);
      }

      return;
    } else {
      switch (type) {
        case Descriptor.Types.DataDescriptor:
        case Descriptor.Types.GenericDescriptor:
          descriptor = Descriptor.applyDefault(Descriptor.Types.DataDescriptor,descriptor,{writable:true});
          type = Descriptor.Types.DataDescriptor;
          break;
        case Descriptor.Types.AccessorDescriptor:
          descriptor = Descriptor.applyDefault(Descriptor.Types.AccessorDescriptor,descriptor);
          break;
        default :
          break;
      }
      // create hooks
      Hooks.retrieve(object,key);
      storeDescriptor(descriptor);
    }
    Object.defineProperty(object,key,{
      get : function __BeautifulProperties_Hookable_get() {
        var descriptor = Descriptor.retrieve(object,key);
        var type = Descriptor.getTypeOf(descriptor);
        var status = Status.retrieve(this,key);
        switch (type) {
          case Descriptor.Types.DataDescriptor:
            if (!status.isInitialized) {
              (internal.init_DataDescriptor)(this,key,Undefined,object);
              if (!status.isInitialized) {
                return;
              }
            }
            (internal.get_beforeGet)(this,key,object);
            return (internal.get_afterGet)(this,key,Raw.retrieve(this,key),object);
          case Descriptor.Types.AccessorDescriptor:
            // write only
            if (!descriptor.get) {
              return undefined;
            }
            var isInitialized = status.isInitialized;
            if (!isInitialized) {
              (internal.init_AccessorDescriptor)(this, key, object);
            }
            (internal.get_beforeGet)(this,key,object);
            if (isInitialized) {
              (internal.get_refreshProperty)(this, key, object);
            }
            return (internal.get_afterGet)(this,key,Raw.retrieve(this,key),object);
          default :
            throw new Error('InvalidState');
        }
      },
      set : function __BeautifulProperties_Hookable_set(val) {
        var descriptor = Descriptor.retrieve(object,key);
        var type = Descriptor.getTypeOf(descriptor);
        var status = Status.retrieve(this,key);
        switch (type) {
          case Descriptor.Types.DataDescriptor:
            // read only
            if (!descriptor.writable) {
              return;
            }
            if (!status.isInitialized) {
              (internal.init_DataDescriptor)(this,key,val,object);
              return;
            }
            var previousVal = Raw.retrieve(this,key);
            val = (internal.set_beforeSet)(this,key,val,previousVal,object);
            Raw.store(this,key,val);
            (internal.set_afterSet)(this,key,val,previousVal,object);
            break;
          case Descriptor.Types.AccessorDescriptor:
            // read only
            if (!descriptor.set) {
              return;
            }
            var previousVal;
            // write only
            if (!descriptor.get) {
              previousVal = undefined;
              val = (internal.set_beforeSet)(this,key,val,previousVal,object);
              descriptor.set.call(this,val);
              // can't refresh
              (internal.set_afterSet)(this,key,val,previousVal,object);
              return;
            }
            // read/write
            if (status.isInitialized) {
              previousVal = Raw.retrieve(this,key);
              val = (internal.set_beforeSet)(this,key,val,previousVal,object);
              descriptor.set.call(this,val);
              (internal.get_refreshProperty)(this, key, object);
              (internal.set_afterSet)(this,key,val,previousVal,object);
            } else {
              (internal.init_AccessorDescriptor)(this, key, object);
              this[key] = val;
            }

            break;
          default :
            throw new Error('InvalidState');
        }
      },
      enumerable:descriptor.enumerable,
      configurable:descriptor.configurable
    });
  };
});