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                 //this.super("constructor", arguments);
509             }
510 
511         };
512         return __define(child, sup, proto);
513     },
514 
515     /**
516      * Defines a singleton instance of a Class
517      * @example
518      *  var MyLab = comb.singleton([Mammal, Wolf, Dog, Breed]);
519      *  var myLab1 = new MyLab();
520      *  myLab1.type = "collie"
521      *  var myLab2 = new MyLab();
522      *  myLab1 === myLab2 => true
523      *  myLab1.type => "collie"
524      *  myLab2.type => "collie"
525      * See {@link define}
526      */
527     singleton : function(sup, proto) {
528         var retInstance;
529         var child = function() {
530             if (!retInstance) {
531                 //if a unique wasn't defined then the
532                 //child didn't define one!
533                 var instance = this.__meta.proto.instance;
534                 if (instance && instance.constructor._unique) {
535                     instance.constructor.apply(this, arguments);
536                 } else {
537                     var supers = this.__meta.supers, l = supers.length;
538                     for (var i = 0; i < l; i++) {
539                         var protoInstance = supers[i].__meta.proto.instance;
540                         if (protoInstance && protoInstance.hasOwnProperty("constructor")) {
541                             protoInstance.constructor.apply(this, arguments);
542                             break;
543                         }
544                     }
545                 }
546                 retInstance = this;
547             }
548             return retInstance;
549         };
550         return __define(child, sup, proto);
551     }
552 });
553