      1 var base = require("./base/object");
      2
      3 /**
      4  * Used to keep track of classes and to create unique ids
      5  * @ignore
      6  */
      7 var classCounter = 0;
      8
      9 /**
     10  * Alias to call the super method of a particular class
     11  *
     12  * @ignore
     13  */
     14 var callSuper = function (args, a) {
     15     if (!args && !a) {
     16         throw new Error("arguments must be defined.");
     17     }
     18     var f = "", u;
     19     var callee, m, meta = this.__meta || {}, pos, supers, l;
     20     if (!a) {
     21         a = args;
     22     }
     23     if (args.callee) {
     24         callee = args.callee;
     25         !f && (f = callee._name);
     26         u = callee._unique;
     27         pos = meta.pos, supers = meta.supers, l = meta.supers.length;
     28         if (f != "constructor") {
     29             if (meta.unique === u) {
     30                 pos = 0;
     31             } else if (callee != meta.callee) {
     32                 pos = 0;
     33                 for (; pos < l; pos++) {
     34                     var sup = supers[pos], sm = sup ? sup[f] : null;
     35                     if (sm == callee) {
     36                         pos++;
     37                         break;
     38                     }
     39                 }
     40             }
     41             for (; pos < l; pos++) {
     42                 var sup = supers[pos], sm = sup ? sup[f] : null;
     43                 if (sm && sm != callee) {
     44                     m = sm;
     45                     break;
     46                 }
     47             }
     48         } else {
     49             if (meta.unique === u) {
     50                 pos = 0;
     51             } else if (callee != meta.callee) {
     52                 pos = 0;
     53                 for (; pos < l; pos++) {
     54                     var sup = supers[pos].__meta.proto;
     55                     if (sup.instance && sup.instance.hasOwnProperty(f)) {
     56                         sm = sup.instance[f];
     57                         if (sm == callee) {
     58                             pos++;
     59                             break;
     60                         }
     61                     }
     62                 }
     63             }
     64             for (; pos < l; pos++) {
     65                 var sup = supers[pos].__meta.proto;
     66                 if (sup.instance && sup.instance.hasOwnProperty(f)) {
     67                     sm = sup.instance[f];
     68                     if (sm && sm != callee) {
     69                         m = sm;
     70                         break;
     71                     }
     72                 }
     73             }
     74         }
     75     } else {
     76         throw new Error("can't find the _super");
     77     }
     78     meta.callee = m;
     79     meta.pos = m ? ++pos : -1;
     80     if (m) {
     81         return m.apply(this, a);
     82     } else {
     83         return null;
     84     }
     85 };
     86
     87 /**
     88  * @ignore
     89  */
     90 var defineMixinProps = function (child, proto, unique) {
     91     if (proto) {
     92         var operations = proto.setters;
     93         if (operations) {
     94             for (var i in operations) {
     95                 if (!child.__lookupSetter__(i)) {  //make sure that the setter isnt already there
     96                     child.__defineSetter__(i, operations[i]);
     97                 }
     98             }
     99         }
    100         operations = proto.getters;
    101         if (operations) {
    102             for (i in operations) {
    103                 if (!child.__lookupGetter__(i)) {
    104                     //define the getter if the child does not already have it
    105                     child.__defineGetter__(i, operations[i]);
    106                 }
    107             }
    108         }
    109         if (proto) {
    110             for (j in proto) {
    111                 if (j != "getters" && j != "setters") {
    112                     if (!child.hasOwnProperty(j)) {
    113                         child[j] = proto[j];
    114                     }
    115                 }
    116             }
    117         }
    118     }
    119 };
    120
    121 /**
    122  * @ignore
    123  */
    124 var mixin = function () {
    125     var args = Array.prototype.slice.call(arguments);
    126     var child = this.prototype, bases = child.__meta.bases, staticBases = bases.slice(), constructor = child.constructor;
    127     for (var i = 0; i < args.length; i++) {
    128         var m = args[i];
    129         var proto = m.prototype.__meta.proto;
    130         defineMixinProps(child, proto.instance, constructor._unique);
    131         defineMixinProps(this, proto.static, constructor._unique);
    132         //copy the bases for static,
    133         var staticSupers = this.__meta.supers, supers = child.__meta.supers;
    134         child.__meta.supers = mixinSupers(m.prototype, bases).concat(supers);
    135         this.__meta.supers = mixinSupers(m, staticBases).concat(staticSupers);
    136     }
    137     return this;
    138 };
    139
    140 /**
    141  * @ignore
    142  */
    143 var mixinSupers = function (sup, bases) {
    144     var arr = [], unique = sup.__meta.unique;
    145     //check it we already have this super mixed into our prototype chain
    146     //if true then we have already looped their supers!
    147     if (bases.indexOf(unique) == -1) {
    148         arr.push(sup);
    149         //add their id to our bases
    150         bases.push(unique);
    151         var supers = sup.__meta.supers, l = supers.length;
    152         if (supers && l) {
    153             for (var i = 0; i < l; i++) {
    154                 arr = arr.concat(mixinSupers(supers[i], bases));
    155             }
    156         }
    157     }
    158     return arr;
    159 };
    160
    161
    162 /**
    163  * @ignore
    164  */
    165 var defineProps = function (child, proto) {
    166     if (proto) {
    167         var operations = proto.setters;
    168         if (operations) {
    169             for (var i in operations) {
    170                 child.__defineSetter__(i, operations[i]);
    171             }
    172         }
    173         operations = proto.getters;
    174         if (operations) {
    175             for (i in operations) {
    176                 child.__defineGetter__(i, operations[i]);
    177             }
    178         }
    179         for (i in proto) {
    180             if (i != "getters" && i != "setters") {
    181                 var f = proto[i];
    182                 if (typeof f == "function") {
    183                     f._name = i;
    184                     f._unique = "define" + classCounter;
    185                 }
    186                 if (i != "constructor") {
    187                     child[i] = f;
    188                 }
    189             }
    190         }
    191     }
    192 };
    193
    194 var _export = function (obj, name) {
    195     if (obj && name) {
    196         obj[name] = this;
    197     } else {
    198         obj.exports = obj = this;
    199     }
    200     return this;
    201 };
    202
    203
    204 /**
    205  * @ignore
    206  */
    207 var __define = function (child, sup, proto) {
    208     proto = proto || {};
    209     var childProto = child.prototype, supers = [];
    210     if (sup instanceof Array) {
    211         supers = sup;
    212         sup = supers.shift();
    213     }
    214     var bases = [], meta, staticMeta;
    215     if (sup) {
    216         child.__proto__ = sup;
    217         childProto.__proto__ = sup.prototype;
    218         meta = childProto.__meta = {
    219             supers:mixinSupers(sup.prototype, bases),
    220             superPos:0,
    221             superName:"",
    222             unique:"define" + classCounter
    223         };
    224         staticMeta = child.__meta = {
    225             supers:mixinSupers(sup, []),
    226             superPos:0,
    227             superName:"",
    228             unique:"define" + classCounter
    229         };
    230     } else {
    231         meta = childProto.__meta = {
    232             supers:[],
    233             superPos:0,
    234             superName:"",
    235             unique:"define" + classCounter
    236         };
    237         staticMeta = child.__meta = {
    238             supers:[],
    239             superPos:0,
    240             superName:"",
    241             unique:"define" + classCounter
    242         };
    243     }
    244     meta.proto = proto;
    245     childProto._static = child;
    246     defineProps(childProto, proto.instance);
    247     defineProps(child, proto.static);
    248     meta.bases = bases;
    249     staticMeta.bases = bases;
    250     if (supers.length) {
    251         mixin.apply(child, supers.reverse());
    252     }
    253     child.mixin = mixin;
    254     child.as = _export;
    255     child._super = callSuper;
    256     childProto._super = callSuper;
    257     classCounter++;
    258     return child;
    259 };
    260
    261
    262 base.merge(exports, {
    263     /**@lends comb*/
    264
    265     /**
    266      * Defines a new class to be used
    267      *
    268      * <p>
    269      *     Class methods
    270      *     <ul>
    271      *         <li>as(module | bject, name): exports the object to module or the object with the name</li>
    272      *         <li>mixin(mixin) : mixes in an object</li>
    273      *     </ul>
    274      *     </br>
    275      *     Instance methods
    276      *     <ul>
    277      *         <li>_super(argumnents, [?newargs]): calls the super of the current method</li>
    278      *     </ul>
    279      *
    280      *      </br>
    281      *     Instance properties
    282      *     <ul>
    283      *         <li>_static: use to reference class properties and methods</li>
    284      *     </ul>
    285      *
    286      * </p>
    287      *
    288      *
    289      * @example
    290      *  //Class without a super class
    291      * var Mammal = comb.define(null, {
    292      *      instance : {
    293      *
    294      *          constructor: function(options) {
    295      *              options = options || {};
    296      *              this._super(arguments);
    297      *              this._type = options.type || "mammal";
    298      *          },
    299      *
    300      *          speak : function() {
    301      *              return  "A mammal of type " + this._type + " sounds like";
    302      *          },
    303      *
    304      *          //Define your getters
    305      *          getters : {
    306      *              type : function() {
    307      *                  return this._type;
    308      *              }
    309      *          },
    310      *
    311      *           //Define your setters
    312      *          setters : {
    313      *              type : function(t) {
    314      *                  this._type = t;
    315      *              }
    316      *          }
    317      *      },
    318      *
    319      *      //Define your static methods
    320      *      static : {
    321      *          soundOff : function() {
    322      *              return "Im a mammal!!";
    323      *          }
    324      *      }
    325      * });
    326      *
    327      * //Show singular inheritance
    328      *var Wolf = comb.define(Mammal, {
    329      *   instance: {
    330      *       constructor: function(options) {
    331      *          options = options || {};
    332      *          //You can call your super constructor, or you may not
    333      *          //call it to prevent the super initializing parameters
    334      *          this._super(arguments);
    335      *          this._sound = "growl";
    336      *          this._color = options.color || "grey";
    337      *      },
    338      *
    339      *      speak : function() {
    340      *          //override my super classes speak
    341      *          //Should return "A mammal of type mammal sounds like a growl"
    342      *          return this._super(arguments) + " a " + this._sound;
    343      *      },
    344      *
    345      *      //add new getters for sound and color
    346      *      getters : {
    347      *
    348      *          color : function() {
    349      *              return this._color;
    350      *          },
    351      *
    352      *          sound : function() {
    353      *              return this._sound;
    354      *          }
    355      *      },
    356      *
    357      *      setters : {
    358      *
    359      *          //NOTE color is read only except on initialization
    360      *
    361      *          sound : function(s) {
    362      *              this._sound = s;
    363      *          }
    364      *      }
    365      *
    366      *  },
    367      *
    368      *  static : {
    369      *      //override my satic soundOff
    370      *      soundOff : function() {
    371      *          //You can even call super in your statics!!!
    372      *          //should return "I'm a mammal!! that growls"
    373      *          return this._super(arguments) + " that growls";
    374      *      }
    375      *  }
    376      *});
    377      *
    378      *
    379      * //Typical hierarchical inheritance
    380      * // Mammal->Wolf->Dog
    381      * var Dog = comb.define(Wolf, {
    382      *    instance: {
    383      *        constructor: function(options) {
    384      *            options = options || {};
    385      *            this._super(arguments);
    386      *            //override Wolfs initialization of sound to woof.
    387      *            this._sound = "woof";
    388      *
    389      *        },
    390      *
    391      *        speak : function() {
    392      *            //Should return "A mammal of type mammal sounds like a growl thats domesticated"
    393      *            return this._super(arguments) + " thats domesticated";
    394      *        }
    395      *    },
    396      *
    397      *    static : {
    398      *        soundOff : function() {
    399      *            //should return "I'm a mammal!! that growls but now barks"
    400      *            return this._super(arguments) + " but now barks";
    401      *        }
    402      *    }
    403      *});
    404      *
    405      *
    406      *
    407      * dog instanceof Wolf => true
    408      * dog instanceof Mammal => true
    409      * dog.speak() => "A mammal of type mammal sounds like a woof thats domesticated"
    410      * dog.type => "mammal"
    411      * dog.color => "gold"
    412      * dog.sound => "woof"
    413      * Dog.soundOff() => "Im a mammal!! that growls but now barks"
    414      *
    415      * // Mammal->Wolf->Dog->Breed
    416      *var Breed = comb.define(Dog, {
    417      *    instance: {
    418      *
    419      *        //initialize outside of constructor
    420      *        _pitch : "high",
    421      *
    422      *        constructor: function(options) {
    423      *            options = options || {};
    424      *            this._super(arguments);
    425      *            this.breed = options.breed || "lab";
    426      *        },
    427      *
    428      *        speak : function() {
    429      *            //Should return "A mammal of type mammal sounds like a
    430      *            //growl thats domesticated with a high pitch!"
    431      *            return this._super(arguments) + " with a " + this._pitch + " pitch!";
    432      *        },
    433      *
    434      *        getters : {
    435      *            pitch : function() {
    436      *                return this._pitch;
    437      *            }
    438      *        }
    439      *    },
    440      *
    441      *    static : {
    442      *        soundOff : function() {
    443      *            //should return "I'M A MAMMAL!! THAT GROWLS BUT NOW BARKS!"
    444      *            return this._super(arguments).toUpperCase() + "!";
    445      *        }
    446      *    }
    447      * });
    448      *
    449      *
    450      * var breed = new Breed({color : "gold", type : "lab"}),
    451      *
    452      *
    453      *
    454      * breed instanceof Dog => true
    455      * breed instanceof Wolf => true
    456      * breed instanceof Mammal => true
    457      * breed.speak() => "A mammal of type lab sounds like a woof "
    458      *                  + "thats domesticated with a high pitch!"
    459      * breed.type => "lab"
    460      * breed.color => "gold"
    461      * breed.sound => "woof"
    462      * breed.soundOff() => "IM A MAMMAL!! THAT GROWLS BUT NOW BARKS!"
    463      *
    464      *
    465      *  //Example of multiple inheritance
    466      *  //NOTE proto is optional
    467      *
    468      *  //Mammal is super class
    469      *  //Wolf Dog and Breed inject functionality into the prototype
    470      * var Lab = comb.define([Mammal, Wolf, Dog, Breed]);
    471      *
    472      * var lab = new Lab();
    473      * lab instanceof Wolf => false
    474      * lab instanceof Dog => false
    475      * lab instanceof Breed => false
    476      * lab instanceof Mammal => true
    477      * lab.speak() => "A mammal of type mammal sounds like a"
    478      *                + " woof thats domesticated with a high pitch!"
    479      * Lab.soundOff() => "IM A MAMMAL!! THAT GROWLS BUT NOW BARKS!"
    480      *
    481      * @param {Array|Class} super the supers of this class
    482      * @param {Object} [proto] the object used to define this class
    483      * @param {Object} [proto.instance] the instance methods of the class
    484      * @param {Object} [proto.instance.getters] the getters for the class
    485      * @param {Object} [proto.instance.setters] the setters for the class
    486      * @param {Object} [proto.static] the Class level methods of this class
    487      * @param {Object} [proto.static.getters] static getters for the object
    488      * @param {Object} [proto.static.setters] static setters for the object
    489      *
    490      * @returns {Object} the constructor of the class to be used with new keyword
    491      */
    492     define:function (sup, proto) {
    493         var child = function () {
    494             //if a unique wasn't defined then the
    495             //child didn't define one!
    496             var instance = this.__meta.proto.instance;
    497             if (instance && instance.constructor && instance.constructor._unique) {
    498                 instance.constructor.apply(this, arguments);
    499             } else {
    500                 var supers = this.__meta.supers, l = supers.length;
    501                 for (var i = 0; i < l; i++) {
    502                     var protoInstance = supers[i].__meta.proto.instance;
    503                     if (protoInstance && protoInstance.hasOwnProperty("constructor")) {
    504                         protoInstance.constructor.apply(this, arguments);
    505                         break;
    506                     }
    507                 }
    508             }
    509
    510         };
    511         var ret = __define(child, sup, proto);
    512         if (typeof ret.init === "function") {
    513             ret.init();
    514         }
    515         return ret;
    516     },
    517
    518     /**
    519      * Defines a singleton instance of a Class
    520      * @example
    521      *  var MyLab = comb.singleton([Mammal, Wolf, Dog, Breed]);
    522      *  var myLab1 = new MyLab();
    523      *  myLab1.type = "collie"
    524      *  var myLab2 = new MyLab();
    525      *  myLab1 === myLab2 => true
    526      *  myLab1.type => "collie"
    527      *  myLab2.type => "collie"
    528      * See {@link define}
    529      */
    530     singleton:function (sup, proto) {
    531         var retInstance;
    532         var child = function () {
    533             if (!retInstance) {
    534                 //if a unique wasn't defined then the
    535                 //child didn't define one!
    536                 var instance = this.__meta.proto.instance;
    537                 if (instance && instance.constructor._unique) {
    538                     instance.constructor.apply(this, arguments);
    539                 } else {
    540                     var supers = this.__meta.supers, l = supers.length;
    541                     for (var i = 0; i < l; i++) {
    542                         var protoInstance = supers[i].__meta.proto.instance;
    543                         if (protoInstance && protoInstance.hasOwnProperty("constructor")) {
    544                             protoInstance.constructor.apply(this, arguments);
    545                             break;
    546                         }
    547                     }
    548                 }
    549                 retInstance = this;
    550             }
    551             return retInstance;
    552         };
    553         var ret = __define(child, sup, proto);
    554         if (typeof ret.init === "function") {
    555             ret.init();
    556         }
    557         return ret;
    558     }
    559 });
    560