1 //##############################################################################
  2 // The $class Library
  3 // Copyright 2006, Jeff Lau
  4 // License: http://creativecommons.org/licenses/LGPL/2.1/
  5 // Contact: jlau@uselesspickles.com
  6 // Web:     www.uselesspickles.com
  7 //##############################################################################
  8 
  9 /**
 10  * @fileOverview
 11  * <p>
 12  * This is the non-debug version of the $class library. It is slimmed down to the bare minimum to make all the
 13  * features work, but with no error checking. This signifigantly reduces overhead to keep your code running fast.
 14  * be sure to switch over to including class-debug.js when you are debugging or developing to make use of all
 15  * the error detection.
 16  * </p>
 17  */
 18 
 19 $class_library = {
 20   version: "2.0b",
 21   
 22   debug: false,
 23 
 24   _globalObject: this,
 25 
 26   _hasOwnProperty: Object.prototype.hasOwnProperty ?
 27     function (obj, prop) {
 28       return obj.hasOwnProperty(prop);
 29     }
 30     :
 31     function (obj, prop) {
 32       return (prop in obj) && obj.constructor.prototype[prop] !== obj[prop];
 33     },
 34 
 35   _surrogateCtor: function() {
 36   }
 37 };
 38 
 39 //##############################################################################
 40 
 41 function $namespace(name) {
 42   // if being called as a constructor...
 43   if (this instanceof $namespace) {
 44     this.toString = function() {
 45        return "[$namespace " + name + "]";
 46     }
 47 
 48     $class_library._surrogateCtor.prototype = this;
 49     return new $class_library._surrogateCtor();
 50   }
 51 
 52   var components = name.split(".");
 53   var context = $class_library._globalObject;
 54 
 55   for (var i = 0; i < components.length; ++i) {
 56     var nextContext = context[components[i]];
 57 
 58     if (!nextContext) {
 59       nextContext = new $namespace(components.slice(0, i + 1).join("."));
 60       context[components[i]] = nextContext;
 61     }
 62 
 63     context = nextContext;
 64   }
 65   
 66   return context;
 67 }
 68 
 69 //##############################################################################
 70 
 71 function $class(name, descriptor) {
 72   // if being called as a constructor...
 73   if (this instanceof $class) {
 74     this._name = name;
 75     this._isNative = descriptor.isNative;
 76     this._ctor = descriptor.ctor;
 77     this._baseCtor = descriptor.baseCtor || (this._ctor == Object ? null : Object);
 78     this._interfaces = descriptor.interfaces || {};
 79     this._children = [];
 80 
 81     // if not creating $class for Object...
 82     if (this._ctor != Object) {
 83       // if inheriting something other than Object...
 84       if (this._baseCtor != Object) {
 85         // if this $class object is being created internally by the $class function...
 86         if (descriptor.calledFrom$class) {
 87           // a 'surrogate' constructor is used to create inheritance relationship
 88           // without actually invoking the base class's constructor code
 89           $class_library._surrogateCtor.prototype = this._baseCtor.prototype;
 90           this._ctor.prototype = new $class_library._surrogateCtor();
 91           this._ctor.prototype.constructor = this._ctor;
 92         }
 93 
 94         var base$class = this._baseCtor.$class;
 95         // inherit info about the base class
 96         // inlined object copy
 97         var copyTo = this._interfaces;
 98         var copyFrom = base$class._interfaces;
 99         for (var prop in copyFrom) { copyTo[prop] = copyFrom[prop]; }
100       
101         base$class._children.push(this);
102       }
103       
104       // store this class info on the prototype
105       this._ctor.prototype._$class = this;
106     }
107     // Storing class info on prototype and constructor causes an IE leak; store created $class objects to clean up when
108     // unloading the page.    
109     $class._cleanupCache.push(this);    
110 
111     return;
112   }
113 
114   // local aliases to reduce scope and property lookups
115   var wrapExtendedMethod = $class._wrapExtendedMethod;
116   var objectProto = Object.prototype;
117 
118   var baseCtor = descriptor.$extends || Object;
119   var namePath = name.split(".");
120   var ctorName = namePath.pop();
121   var ctor = descriptor.$constructor || descriptor[ctorName];
122 
123   if (!baseCtor.$class) {
124     $class.adapt(baseCtor, name + "$base");
125   }
126 
127   if (!ctor || $class._isTrivialCtor.test(ctor)) {
128     if (baseCtor._$class_isTrivialCtor) {
129       ctor = $class._createDefaultCtor();
130       ctor._$class_isTrivialCtor = true; 
131     } else {
132       var relevantBase = baseCtor._$class_relevantImplementation || baseCtor;
133       ctor = $class._createDefaultExtendedCtor(relevantBase);
134       ctor._$class_relevantImplementation = relevantBase;
135     }
136   } else if (ctor.toString().indexOf("this.$base") != -1) {
137     ctor = wrapExtendedMethod(ctor, baseCtor._$class_relevantImplementation || baseCtor);
138   } else if (!baseCtor._$class_isTrivialCtor) {
139     ctor = $class._wrapExtendedCtor(ctor, baseCtor._$class_relevantImplementation || baseCtor);
140   }
141   
142   ctor.$class = new $class(name, {ctor:ctor, baseCtor:baseCtor, calledFrom$class:true});
143   var proto = ctor.prototype;
144 
145   if (descriptor.$singleton) {
146     var createInstance = (typeof descriptor.$singleton.createInstance == "function") ? descriptor.$singleton.createInstance : $class._createSingletonInstance_implementation;
147 
148     descriptor._$class_createSingletonInstance = $static(createInstance);
149     descriptor._$class_singletonInstance = $static(null);
150     descriptor.getInstance = $static($class._getSingletonInstance_implementation);
151   }
152 
153   if (descriptor.$multiton) {
154     var createInstance = (typeof descriptor.$multiton.createInstance == "function") ? descriptor.$multiton.createInstance : $class._createMultitonInstance_implementation;
155     var createCacheKey = (typeof descriptor.$multiton.createCacheKey == "function") ? descriptor.$multiton.createCacheKey : $class._createMultitonCacheKey_implementation;
156 
157     descriptor._$class_createMultitonInstance = $static(createInstance);
158     descriptor._$class_createMultitonCacheKey = $static(createCacheKey);
159     descriptor._$class_multitonInstances = $static({});
160     descriptor.getInstance = $static($class._getMultitonInstance_implementation);
161   }
162 
163   // implement interfaces
164   if (descriptor.$implements != null) {
165     var ifaces = descriptor.$implements;
166 
167     // convert to an array if it is a single object
168     if (!(ifaces instanceof Array)) {
169       ifaces = [ifaces];
170     }
171 
172     for (var i = 0, iface; iface = ifaces[i]; ++i) {
173       // inlined object copy
174       var copyTo = ctor.$class._interfaces;
175       var copyFrom = iface._interfaces;
176       for (var prop in copyFrom) { copyTo[prop] = copyFrom[prop]; }
177     }
178   }
179 
180   var specialProperties = $class._specialProperties;
181   var processedProperties = {};
182   specialProperties[ctorName] = true;
183 
184   // process all properties in the class descriptor
185   for (var propertyName in descriptor) {
186     // skip over special properties
187     if (specialProperties[propertyName] === true) {
188       continue;
189     }
190 
191     var value = descriptor[propertyName];
192     
193     if (value != null && value._$class_modifiedProperty) {
194       // In the release version, only the static modifier results in a
195       // $class._ModifiedProperty object, so there is no need to check
196       // the modifier value
197       ctor[propertyName] = value.value;
198     } else if (typeof value == "function" && value.toString().indexOf("this.$base") != -1) {
199       // wrap the method to give it access to the special $base property
200       proto[propertyName] = wrapExtendedMethod(value, proto[propertyName]);
201     } else {
202       proto[propertyName] = value;
203     }
204     
205     processedProperties[propertyName] = true;
206   }
207   
208   specialProperties[ctorName] = false;
209   
210   // These are property names that IE mistakenly skips while iterating over an object's properties (only the standard 
211   // properties on Object's prototype are supposed to be skipped; a property of the same name that is created directly 
212   // on an object should be iterated)
213   var skippedByIE = $class._propertiesSkippedByIE;
214   
215   for (var i = 0, propertyName; propertyName = skippedByIE[i]; ++i) {
216     var value = descriptor[propertyName];
217 
218     if (value !== objectProto[propertyName] && processedProperties[propertyName] !== true) {
219       if (value.toString().indexOf("this.$base") != -1) {
220         // wrap the method to give it access to the special $base property
221         proto[propertyName] = wrapExtendedMethod(value, proto[propertyName]);
222       } else {
223         proto[propertyName] = value;
224       }
225     }
226   }
227 
228   // store the constructor so it is accessible by the specified name
229   var scope = $class_library._globalObject;
230   
231   for (var i = 0, pathComponent; pathComponent = namePath[i]; ++i) {
232     scope = scope[pathComponent];
233   } 
234   
235   var result = scope[ctorName] = ctor;
236 
237   //  call the static initializer
238   if (descriptor.$static) {
239     descriptor.$static.call(ctor);
240   }
241 
242   return result;
243 }
244 
245 //##############################################################################
246 
247 $class.prototype = {
248   getName: function() {
249     return this._name;
250   },
251 
252   isNative: function() {
253     return this._isNative;
254   },
255 
256   getPrototype: function() {
257     return this._ctor.prototype;
258   },
259 
260   getConstructor: function() {
261     return this._ctor;
262   },
263 
264   getSuperclass: function() {
265     return this._baseCtor ? this._baseCtor.$class : null;
266   },
267 
268   isInstance: function(obj) {
269     return $class.instanceOf(obj, this._ctor);
270   },
271 
272   implementsInterface: function(iface) {
273     return $class_library._hasOwnProperty(this._interfaces, iface.getName());
274   },
275 
276   toString: function() {
277     return "[$class " + this._name + "]";
278   }
279 };
280 
281 //##############################################################################
282 // List of $class objects created so they can be cleaned up when unloading the page
283 $class._cleanupCache = [];
284 
285 // using attachEvent because this is only needed to avoid memory leaks in IE, and only IE supports attachEvent.
286 if (typeof window != "undefined" && window.attachEvent) {
287   window.attachEvent('onunload', function() {
288     for (var i=0,clz; clz = $class._cleanupCache[i]; i++) { 
289       delete clz._ctor.prototype._$class;
290       delete clz._ctor.$class;
291     }
292   });
293 }
294 
295 //##############################################################################
296 
297 $class._isTrivialCtor = /^\s*function[^(]*\([^)]*\)[^{]*\{\s*(return)?\s*;?\s*\}\s*$/;
298 
299 $class._specialProperties = {$constructor:true,$singleton:true,$multiton:true,$extends:true,$implements:true,$static:true};
300 
301 $class._propertiesSkippedByIE = ["valueOf", "toString"];
302 
303 //##############################################################################
304 
305 $class._wrapExtendedMethod = function(method, baseMethod) {
306   var result = function() {
307     var h=this.$base;
308     this.$base=baseMethod;
309     try{return method.apply(this,arguments);}
310     finally{this.$base=h;}
311   };
312 
313   result.toString = function() { return method.toString(); };
314 
315   return result;
316 };
317 
318 //##############################################################################
319 
320 $class._createDefaultCtor = function() {
321   return function() {};
322 };
323 
324 //##############################################################################
325 
326 $class._createDefaultExtendedCtor = function(baseMethod) {
327   return function() {
328     baseMethod.apply(this, arguments);
329   };
330 };
331 
332 //##############################################################################
333 
334 $class._wrapExtendedCtor = function(method, baseMethod) {
335   var result = function() {
336     baseMethod.apply(this, arguments);
337     method.apply(this, arguments);
338   };
339 
340   result.toString = function() { return method.toString(); };
341 
342   return result;
343 };
344 
345 //##############################################################################
346 
347 $class._getSingletonInstance_implementation = function() {
348   return this._$class_singletonInstance || (this._$class_singletonInstance = this._$class_createSingletonInstance());
349 };
350 
351 //##############################################################################
352 
353 $class._createSingletonInstance_implementation = function() {
354   return new this();
355 };
356 
357 //##############################################################################
358 
359 $class._getMultitonInstance_implementation = function() {
360   var cacheKey = this._$class_createMultitonCacheKey.apply(this, arguments);
361 
362   return this._$class_multitonInstances[cacheKey] || (this._$class_multitonInstances[cacheKey] = this._$class_createMultitonInstance.apply(this, arguments));
363 };
364 
365 //##############################################################################
366 
367 $class._createMultitonInstance_implementation = function() {
368   return $class.instantiate(this, arguments);
369 };
370 
371 //##############################################################################
372 
373 $class._createMultitonCacheKey_implementation = function() {
374   return Array.prototype.join.call(arguments, "|");
375 };
376 
377 //##############################################################################
378 
379 $class.adapt = (function() {
380   var unnamedCount = 0;
381   
382   return function(ctor, name, isNative) {
383     if (ctor.$class) {
384       return false;
385     }
386 
387     var baseCtor = ctor.prototype.constructor;
388 
389     if (ctor != Object) {
390       var hold = ctor.prototype.constructor;
391 
392       if (delete ctor.prototype.constructor) {
393         baseCtor = ctor.prototype.constructor;
394         ctor.prototype.constructor = hold;
395       }
396     }
397 
398     if (!name) {
399       name = "$unnamed_adapted_class_" + (unnamedCount++);
400     }
401 
402     if (baseCtor == ctor || !baseCtor) {
403       baseCtor = Object;
404     } else if (!baseCtor.$class) {
405       $class.adapt(baseCtor, name + "$base");
406     }
407 
408     if ($class._isTrivialCtor.test(ctor)) {
409       if (baseCtor._$class_isTrivialCtor) {
410         ctor._$class_isTrivialCtor = true; 
411       } else {
412         var relevantBase = baseCtor._$class_relevantImplementation || baseCtor;
413         ctor._$class_relevantImplementation = relevantBase;
414       }
415     }
416     
417     var interfaces = ctor.prototype._$class_interfaces;
418     delete ctor.prototype._$class_interfaces;
419     
420     ctor.$class = new $class(name, {
421       ctor: ctor, 
422       baseCtor: baseCtor, 
423       isNative: isNative, 
424       interfaces: interfaces,
425       calledFrom$class_adapt: true
426     }); 
427 
428     return true;
429   };
430 })();
431 
432 //##############################################################################
433 
434 $class.resolve = function(name) {
435   var components = name.split(".");
436   var context = $class_library._globalObject;
437 
438   for (var i = 0; i < components.length; ++i) {
439     if (context == null) {
440       return undefined;
441     }
442     
443     var context = context[components[i]];
444   }
445   
446   return context;
447 };
448 
449 //##############################################################################
450 
451 $class.implementationOf = function(obj, iface) {
452   if (obj._$class_interfaces && $class_library._hasOwnProperty(obj._$class_interfaces, iface.getName())) {
453     return true;
454   }
455 
456   return $class.getClass(obj).implementsInterface(iface);
457 };
458 
459 //##############################################################################
460 
461 $class.instanceOf = function(obj, type) {
462   if (type instanceof $interface) {
463     return $class.implementationOf(obj, type);
464   }
465 
466   switch (typeof obj) {
467     case "object":
468       return (obj instanceof type) ||
469              // special case for null
470              (obj === null && type == Null);
471 
472     case "number":
473       return (type == Number);
474 
475     case "string":
476       return (type == String);
477 
478     case "boolean":
479       return (type == Boolean);
480 
481     case "function":
482     default:
483       return (obj instanceof type);
484 
485     case "undefined":
486       return (type == Undefined);
487   }
488 };
489 
490 //##############################################################################
491 
492 $class.typeOf = function(obj) {
493   return $class.getClass(obj).getName();
494 };
495 
496 //##############################################################################
497 
498 $class.getClass = function(obj) {
499   if (obj == null) {
500     if (obj === undefined) {
501       return Undefined.$class;
502     }
503 
504     return Null.$class;
505   }
506 
507   return obj._$class || Object.$class;
508 };
509 
510 //##############################################################################
511 
512 $class.instantiate = function(ctor, args) {
513   if (ctor.$class && ctor.$class.isNative()) {
514     return ctor.apply($class_library._globalObject, args);
515   } else {
516     $class_library._surrogateCtor.prototype = ctor.prototype;
517     var result = new $class_library._surrogateCtor();
518     ctor.apply(result, args);
519 
520     return result;
521   }
522 };
523 
524 //##############################################################################
525 
526 function $interface(name, descriptor) {
527   // if being called as a constructor...
528   if (this instanceof $interface) {
529     this._name = name;
530     this._methods = {};
531     this._methodsArray = null;
532     this._interfaces = {};
533     this._interfaces[name] = this;
534     return;
535   }
536 
537   var iface = new $interface(name);
538 
539   // extend interfaces
540   if (descriptor.$extends != null) {
541     var ifaces = descriptor.$extends;
542 
543     // convert to an array if it is a single object
544     if (!(ifaces instanceof Array)) {
545       ifaces = [ifaces];
546     }
547 
548     for (var i = 0, ifacesLength = ifaces.length; i < ifacesLength; ++i) {
549       // inlined object copy
550       var copyTo = iface._methods; 
551       var copyFrom = ifaces[i]._methods;
552       for (var prop in copyFrom) { copyTo[prop] = copyFrom[prop]; }
553 
554       // inlined object copy
555       var copyTo = iface._interfaces; 
556       var copyFrom = ifaces[i]._interfaces;
557       for (var prop in copyFrom) { copyTo[prop] = copyFrom[prop]; }
558     }
559   }
560 
561   // process all properties in the descriptor
562   for (var propertyName in descriptor) {
563     // skip over the special properties
564     if (propertyName == "$extends") {
565       continue;
566     }
567 
568     var value = descriptor[propertyName];
569 
570     if (value._$class_modifiedProperty) {
571       iface[propertyName] = value.value;
572     } else {
573       iface._methods[propertyName] = iface._name;
574     }
575   }
576 
577   var namePath = name.split(".");
578   var ifaceName = namePath.pop();
579   // store the interface so it can be referenced by the specified name
580   var scope = $class_library._globalObject;
581   
582   for (var i = 0, pathComponent; pathComponent = namePath[i]; ++i) {
583     scope = scope[pathComponent];
584   } 
585   
586   return scope[ifaceName] = iface;
587 }
588 
589 //##############################################################################
590 
591 $interface.prototype = {
592   getName: function() {
593     return this._name;
594   },
595   
596   hasMethod: function(methodName) {
597     return Boolean(this._methods[methodName]);
598   },
599 
600   getMethods: function() {
601     if (!this._methodsArray) {
602       this._methodsArray = [];
603       
604       for (var methodName in this._methods) {
605         this._methodsArray.push(methodName);
606       }
607     }
608     
609     return this._methodsArray;
610   },
611 
612   applyInterface: function(obj) {
613     var classInfo = obj instanceof $class ? obj : obj.$class;
614 
615     if (classInfo) {
616       if (!classInfo.implementsInterface(this)) {
617         this._applyInterface([classInfo]);
618       }
619     } else {
620       if (obj.constructor == Function) {
621         obj = obj.prototype;
622       }
623       
624       if (!$class.implementationOf(obj, this)) {
625         if (!obj._$class_interfaces) {
626           obj._$class_interfaces = {};
627         }
628 
629         for (var ifaceName in this._interfaces) {
630           obj._$class_interfaces[ifaceName] = this._interfaces[ifaceName];
631         }
632       }
633     }
634 
635     return obj;
636   },
637 
638   _applyInterface: function(classes) {
639     for (var i = 0, classInfo; classInfo = classes[i]; ++i) {
640       for (var ifaceName in this._interfaces) {
641         classInfo._interfaces[ifaceName] = this._interfaces[ifaceName];
642       }
643 
644       this._applyInterface(classInfo._children);
645     }
646   },
647 
648   wrapObject: function(obj) {
649     var result = {_obj: obj};
650 
651     for (var methodName in this._methods) {
652       result[methodName] = $interface._createWrappedObjectMethod(methodName);
653     }
654 
655     return this.applyInterface(result);
656   },
657   
658   toString: function() {
659     return "[$interface " + this._name + "]";
660   }
661 };
662 
663 $interface._createWrappedObjectMethod = function(methodName) {
664   return function() {
665     return this._obj[methodName].apply(this._obj, arguments);
666   };
667 };
668 
669 //##############################################################################
670 
671 function $abstract(method) {
672   // abstract modifier always ignored in release version
673   return {};
674 }
675 
676 function $static(value) {
677   return {value: value, _$class_modifiedProperty: true};
678 }
679 
680 function $final(method) {
681   // final modifier always ignored in release version
682   return method;
683 }
684 
685 //##############################################################################
686 
687 (function() {
688   var nativeClassNames = [
689     "Object",
690     "Array",
691     "String",
692     "Number",
693     "Boolean",
694     "Function",
695     "RegExp",
696     "Date",
697 
698     "Error",
699     "EvalError",
700     "RangeError",
701     "ReferenceError",
702     "SyntaxError",
703     "TypeError",
704     "URIError"
705   ];
706   
707   Object._$class_isTrivialCtor = true;
708   
709   for (var i = 0, className; className = nativeClassNames[i]; ++i) {
710     $class.adapt(this[className], className, true);
711   }
712 })();
713 
714 $class.adapt($namespace, "$namespace");
715 $class.adapt($class,     "$class");
716 $class.adapt($interface, "$interface");
717 
718 //##############################################################################
719 
720 $class("Undefined", {
721   Undefined: $final(function() {
722     throw new Error("Attempted instantiation of the Undefined class.");
723   })
724 });
725 
726 $class("Null", {
727   Null: $final(function() {
728     throw new Error("Attempted instantiation of the Null class.");
729   })
730 });
731 
732 //##############################################################################
733