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