1 /******************************************************************************* 2 * Class.js is a tool for generating JavaScript constructor functions that 3 * can create fully encapsulated instance objects with an inheritance chain 4 * similar to what one would expect from an object-oriented language without 5 * requiring a cross-compiler or incurring much performance overhead. 6 * @module java-class 7 * @author Ranando D. King 8 * @version 3.0.0 9 ******************************************************************************/ 10 'use strict' 11 12 /* 13 These are data keys that help define the Class. Public scope isn't listed 14 here because the Public scope of a Class is its prototype, and the Public 15 Static scope for the Class is the type constructor function that was 16 generated. 17 */ 18 var METADATA = "__$METADATA$__", 19 PRIVATESCOPE = "__$PRIVATESCOPE$__", 20 PROTECTEDSCOPE = "__$PROTECTEDSCOPE$__", 21 STATICSCOPE = "__$STATICSCOPE$__"; 22 23 //List of words reserved for use in a Class definition object. 24 var DefinitionWords = [ "Mode", "Implements", "Mixins", "Extends", 25 "Events", "Constructor", "StaticConstructor", 26 "Self", "Sibling", "Delegate"]; 27 var ReservedWords = [ "Mode", "Implements", "Mixins", "Extends", 28 "Events", "Constructor", "StaticConstructor", 29 "Self", "Sibling", "Delegate", "__name", 30 "isClass", "classMode", "inheritsFrom", 31 "getInterface", "isClassInstance", "__static" ]; 32 33 /** 34 * Enum - Provides the ability to use syntax-checked enumeration values. 35 * 36 * @typedef {Object} Enum 37 */ 38 var Enum = require("Enum"); 39 40 /** 41 * WeakMap - Provides an ES5 shim for the features of ES6 WeakMaps. 42 * 43 * @typedef {Object} WeakMap 44 */ 45 var WeakMap = require("WeakMap"); 46 47 /** 48 * Privilege - An enumeration of the possible privilege levels of Class members. 49 * Defaults to "None`" 50 * 51 * @typedef {Enum} Privilege 52 * @prop {number} None - Specifies an unmarked privilege state. 53 * @prop {number} Public - Marks the member as world visible. 54 * @prop {number} Protected - Marks the member as descendant visible. 55 * @prop {number} Private - Marks the member as exclusively for the defined Class. 56 */ 57 var Privilege = new Enum("None", [ "Public", "Protected", "Private", "None" ]); 58 59 /** 60 * ClassModes - An enumeration of the inheritability of defined Class types. 61 * 62 * @typedef {Enum} ClassModes 63 * @prop {number} Default - Sets unrestricted inheritability for the new Class. 64 * @prop {number} Abstract - Sets required inheritability for the new Class. 65 * @prop {number} Final - Restricts all inheritability of the new Class. 66 */ 67 var ClassModes = new Enum("Default", ["Default", "Abstract", "Final"]); 68 69 /** 70 * ClassDefError - An exception class used to flag errors in Class definitions. 71 * 72 * @class ClassDefError 73 * @extends Error 74 * @param {string} message - String content of the error message to be printed 75 * when thrown. 76 * @param {string} key - Key in the Class definition object where the errors was 77 * found. 78 */ 79 function ClassDefError(message, key) { 80 this.name = "ClassDefError"; 81 this.key = key; 82 this.message = (key?"While processing '" + key + "' - ": "") + message; 83 } 84 ClassDefError.prototype = Error.prototype; 85 86 /** 87 * ClassError - An exception class used to flag runtime errors in Class. 88 * 89 * @class ClassError 90 * @extends Error 91 * @param {string} message - String content of the error message to be printed 92 * when thrown. 93 */ 94 function ClassError(message) { 95 this.name = "ClassError"; 96 this.message = "While running: " + message; 97 } 98 ClassError.prototype = Error.prototype; 99 100 function isSimpleFunction(obj) { 101 return ((obj instanceof Function) && 102 !(obj.isClass || obj.isEnum || obj.isInterface || obj.isAttribute)); 103 } 104 105 function isValidType(type, value) { 106 return ((value === null) || (value === undefined) || 107 (type.isInterface && type.isImplementedBy(value)) || 108 (type.isClass && (value instanceof type)) || 109 ((type === Function) && (value instanceof Function)) || 110 ((type === Date) && (value instanceof Date)) || 111 ((type === String) && (typeof value == "string")) || 112 ((typeof type == "string") && (type.toLowerCase() == typeof value))); 113 } 114 115 function typeConstructor() { 116 var classType = this.constructor; 117 var name = classType.name; 118 var metaData = classType[METADATA]; 119 var childDomain = arguments[arguments.length -1]; 120 var self = arguments[arguments.length -2]; 121 var argc = arguments.length - 2; 122 123 if (!(childDomain && (childDomain.__isInheritedDomain || 124 childDomain.__isDescendant))) { 125 childDomain = null; 126 self = null; 127 argc += 2; 128 } 129 130 if (self && !self.isClassInstance) { 131 self = null; 132 ++argc; 133 } 134 135 if ((metaData.classMode === ClassModes.Abstract) && 136 (!childDomain || !(childDomain.__isInheritedDomain || childDomain.__isDescendant))) 137 throw new SyntaxError("Cannot construct an Abstract Class!"); 138 139 if (!metaData.Constructor || 140 ((metaData.Constructor instanceof Box) && 141 (metaData.Constructor.isPublic || 142 (childDomain && childDomain.__isInheritedDomain && metaData.Constructor.isProtected)))) { 143 initialize(this, childDomain, self); 144 var instance = instances.get(this); 145 if (metaData.Mixins) 146 BlendMixins(definition.Mixins, instance); 147 148 Object.seal(instance); 149 150 var args = [].slice.call(arguments, 0, argc); 151 152 if (classConstructor) { 153 if (!(childDomain && (childDomain.__isInheritedDomain || childDomain.__isDescendant))) { 154 if (this.InheritsFrom) { 155 var hasSuperFirst = /function\s+\w+\s*\((\w+\s*(,\s*\w+)*)?\)\s*{\s*this\s*\.\s*Super\s*\(/; 156 var hasSuper = /\s*this\s*\.\s*Super\s*\(/; 157 var constructorString = classConstructor.value.toString(); 158 159 if (!hasSuper.test(constructorString)) { 160 console.warn("Calling this.Super() for you!!!. You should be doing this in your " + name + " constructor!"); 161 162 if (instance.Super.length) 163 throw new Error("No default constructor available in " + name + "\'s super class!"); 164 165 instance.Super(); 166 } 167 else { 168 if (!hasSuperFirst.test(constructorString)) 169 console.warn("Super should be the first call in your " + name + " constructor!") 170 } 171 } 172 classConstructor.value.apply(instance, args); 173 } 174 } 175 else if (this.InheritsFrom) { 176 instance.Super(); 177 } 178 } 179 else if (classConstructor) 180 throw new Error("Constructor '" + name + "' is not accessible!"); 181 182 return this; 183 } 184 185 function generateTypeConstructor(name) { 186 var callErrorString = "This is a class instance generating function." + 187 "You must use 'new " + (name || "<ClassName>") + 188 "(...)' to use this function."; 189 190 eval("var retval = function " + name + "() {\n" + 191 " if (!(this instanceof retval)) {\n" + 192 " throw new ClassError(callErrorString);\n" + 193 " }\n" + 194 " \n" + 195 " return typeConstructor.apply(this, arguments);\n" + 196 "};"); 197 198 return retval; 199 } 200 201 function validateDefinitionKeys(definition) { 202 var mKey; 203 for (var key in definition) { 204 if (definition.hasOwnProperty(key)) { 205 var member = definition[key]; 206 207 /* 208 If the member isn't an instance of Box, then it's either one of 209 the Class definition description keys or it's just a public 210 member that's been made public by default. 211 */ 212 if (!(member instanceof Box)) { 213 switch(key) { 214 case "Mode": 215 //Since we're keeping the metadata, we only validate. 216 if (!ClassModes.isMember(member)) 217 throw new ClassDefError("Invalid Mode!", "Mode"); 218 break; 219 case "Implements": 220 if (Array.isArray(member)) { 221 for (mKey in member) { 222 if (member.hasOwnProperty(mKey)) { 223 if (!member[mKey].isInterface) 224 throw new ClassDefError("Invalid interface!", key); 225 } 226 } 227 } 228 else 229 throw new ClassDefError("Not an array of Interfaces!", key); 230 231 break; 232 case "Mixins": 233 if (!Array.isArray(member)) 234 throw new ClassDefError("Not an array!", key); 235 236 for (mKey in member) { 237 if (member.hasOwnProperty(mKey)) { 238 if (member[mKey] instanceof Function) 239 throw new ClassDefError("Cannot mixin functions!", key); 240 } 241 } 242 break; 243 case "Extends": 244 if (!(member instanceof Function)) 245 throw new ClassDefError("Cannot extend non-function!", key); 246 break; 247 case "Events": 248 if (!Array.isArray(member)) 249 throw new ClassDefError("Not an array!", key); 250 251 for (var i = 0; i < member.length; i++) { 252 if (typeof member[i] !== "string") 253 throw new ClassDefError("Non-string event name!", key); 254 } 255 break; 256 case "Constructor": 257 case "StaticConstructor": 258 if (!(member instanceof Function)) 259 throw new ClassDefError("Must be a function!", key); 260 261 definition[key] = modifyBox(null, {isPublic: true}, member); 262 break; 263 default: 264 throw new ClassDefError("Unrecognized key!", key); 265 } 266 } 267 else { 268 switch(key) { 269 case "Constructor": 270 if (member.isStatic) 271 throw new ClassDefError("Cannot be Static!", key); 272 273 if (member.isProperty) 274 throw new ClassDefError("Cannot be a Property!", key); 275 276 if (!(member.value instanceof Function)) 277 throw new ClassDefError("Must be a function!", key); 278 279 break; 280 case "StaticConstructor": 281 if (member.isPrivate) 282 throw new ClassDefError("Cannot be Private!", key); 283 284 if (member.isProtected) 285 throw new ClassDefError("Cannot be Protected!", key); 286 287 if (member.isProperty) 288 throw new ClassDefError("Cannot be a Property!", key); 289 290 if (member.isFinal) 291 throw new ClassDefError("Cannot be Final!", key); 292 293 if (!(member.value instanceof Function)) 294 throw new ClassDefError("Must be a function!", key); 295 296 break; 297 default: 298 //Nothing to do here. It's just good form... 299 break; 300 } 301 } 302 } 303 } 304 } 305 306 function generateScopes(obj, definition) { 307 var retval = {}; 308 retval[PRIVATESCOPE] = {}; 309 retval[PROTECTEDSCOPE] = {}; 310 retval[STATICSCOPE] = {}; 311 312 validateDefinitionKeys(definition); 313 314 return retval; 315 } 316 317 function generateMetaData(obj, name, definition) { 318 obj[METADATA] = { 319 name: name, 320 definition: definition, 321 isClass: true, 322 classMode: function getClassMode() { return _classMode; }, 323 inheritsFrom: function inheritsFrom(obj) { 324 return (definition.hasOwnProperty("Extends") && 325 definition.Extends.isClass && 326 ((definition.Extends === obj) || 327 (definition.Extends.inheritsFrom(obj)))); 328 }, 329 getInterface: function getInterface() { 330 var intDef = {}; 331 332 if (obj.InheritsFrom) 333 intDef.Extends = [ obj.InheritsFrom.getInterface() ]; 334 335 if (definition.Implements && Array.isArray(definition.Implements)) { 336 if (!Array.isArray(intDef.Extends)) 337 intDef.Extends = []; 338 339 for (var i=0; i< definition.Implements.length; ++i) 340 intDef.Extends.push(definition.Implements[i]); 341 } 342 343 intDef.Properties = {}; 344 intDef.Methods = {}; 345 346 for(var elementKey in definition) { 347 if (definition.hasOwnProperty(elementKey)) { 348 var element = definition[elementKey]; 349 350 if ((elementKey != "Constructor") && (elementKey != "StaticConstructor") && 351 (element.isBox && element.isPublic)) { 352 353 if (element.isProperty) 354 intDef.Properties[elementKey] = element.value.hasOwnProperty("set"); 355 else 356 intDef.Methods[elementKey] = element.value.length; 357 } 358 } 359 } 360 361 return new Interface(name + "_Interface", intDef); 362 }, 363 }; 364 365 Object.freeze(obj[METADATA]); 366 Object.defineProperty(obj.prototype, "isClassInstance", { value: true }); 367 } 368 369 var Box = (function _Box() { 370 371 var internal = new WeakMap(); 372 373 /** 374 * Box - The metadata object used to contain the description of a member of a 375 * defined Class type. 376 * 377 * @class Box 378 * @param {Object} params - Parameter block encoding the state of each flag. 379 * @property {Privilege} params.privilege - The Privilege for this member. 380 * @property {boolean} params.isFinal - Can it be overridden or changed? 381 * @property {boolean} params.isAbstract - Must it be overridden to be used? 382 * @property {boolean} params.isStatic - Is it owned by the Class type? 383 * @property {boolean} params.isProperty - Does it have side effects? 384 * @property {boolean} params.isDelegate - Will it be called externally? 385 * @property {string} params.type - The specific type returned by this member. 386 * @property {*} params.value - The statically assigned value for this member. 387 */ 388 var retval = function Box(params) { 389 internal.set(this, { 390 privilege: params.privilege, 391 isFinal: !!params.isFinal, 392 isAbstract: !!params.isAbstract, 393 isStatic: !!params.isStatic, 394 isProperty: !!params.isProperty, 395 isDelegate: !!params.isDelegate, 396 type: params.type, 397 value: params.value 398 }); 399 400 if (this.isProperty && this.isFinal) 401 throw new ClassDefError("Isn't this self-contradicting? \"Final Property\" just as allowable as (+1 === -1). Just don't!"); 402 403 if (this.isAbstract && this.isProperty) 404 throw new ClassDefError("Have ye gone daft? How can you override a property that the owning class doesn't even define?"); 405 406 if (this.isAbstract && this.isFinal) 407 throw new ClassDefError("Please make up your mind! Do you want to define it now(\"Final\") or later(\"Abstract\")?"); 408 409 return this; 410 }; 411 412 Object.defineProperties(retval.prototype, { 413 /** 414 * @memberof Box 415 * @property {boolean} isLocked - Flag signifying whether or not the Box 416 * can be edited further. Once locked, the box cannot be unlocked. 417 */ 418 isLocked: { 419 enumerable: true, 420 get: function getIsLocked() { return !!internal.get(this).isLocked; }, 421 set: function setIsLocked(val) { 422 if (val) 423 internal.get(this).isLocked = val; 424 } 425 }, 426 /** 427 * @memberof Box 428 * @property {boolean} isBox - Flag identifying that this a Box and not 429 * a JavaScript value. 430 */ 431 isBox: { 432 enumerable: true, 433 value: true 434 }, 435 /** 436 * @memberof Box 437 * @property {boolean} noPrivilege - Internal flag set to true when no 438 * privilege level is specified for the member. 439 * @readonly 440 */ 441 noPrivilege: { 442 enumerable: false, 443 get: function getNoPrivilege() { return internal.get(this).privilege === Privilege.None; } 444 }, 445 /** 446 * @memberof Box 447 * @property {boolean} isPrivate - Flag identifying that this member is 448 * only accessible internally on direct instances the declaring Class 449 * via 'this'. 450 */ 451 isPrivate: { 452 enumerable: true, 453 get: function getIsPrivate() { return internal.get(this).privilege === Privilege.Private; }, 454 set: function setIsPrivate(val) { 455 var that = internal.get(this); 456 if (!that.isLocked) { 457 if (val) { 458 if (that.privilege !== Privilege.Private) { 459 if (that.privilege === Privilege.None) { 460 that.privilege = Privilege.Private; 461 } 462 else { 463 throw new ClassDefError("Member cannot be both 'Private' and '" + 464 that.privilege.name + "' at the same time!"); 465 } 466 } 467 } 468 else if (that.privilege === Privilege.Private) { 469 that.privilege = Privilege.None; 470 } 471 } 472 } 473 }, 474 /** 475 * @memberof Box 476 * @property {boolean} isProtected - Flag identifying that this member 477 * is accessible internally via this in all instances of the declaring 478 * class. 479 */ 480 isProtected: { 481 enumerable: true, 482 get: function getIsProtected() { return internal.get(this).privilege === Privilege.Protected; }, 483 set: function setIsProtected(val) { 484 var that = internal.get(this); 485 if (!that.isLocked) { 486 if (val) { 487 if (that.privilege !== Privilege.Protected) { 488 if (that.privilege === Privilege.None) { 489 that.privilege = Privilege.Protected; 490 } 491 else { 492 throw new ClassDefError("Member cannot be both 'Protected' and '" + 493 that.privilege.name + "' at the same time!"); 494 } 495 496 } 497 } 498 else if (that.privilege === Privilege.Protected) { 499 that.privilege = Privilege.None; 500 } 501 } 502 } 503 }, 504 /** 505 * @memberof Box 506 * @property {boolean} isPublic - Flag identifying that this member 507 * is accessible externally on all descendants of the declaring class. 508 */ 509 isPublic: { 510 enumerable: true, 511 get: function getIsPublic() { return internal.get(this).privilege === Privilege.Public; }, 512 set: function setIsPublic(val) { 513 var that = internal.get(this); 514 if (!that.isLocked) { 515 if (val) { 516 if (that.privilege !== Privilege.Public) { 517 if (that.privilege === Privilege.None) { 518 that.privilege = Privilege.Public; 519 } 520 else { 521 throw new ClassDefError("Member cannot be both 'Public' and '" + 522 that.privilege.name + "' at the same time!"); 523 } 524 525 } 526 } 527 else if (that.privilege === Privilege.Public) { 528 that.privilege = Privilege.None; 529 } 530 } 531 } 532 }, 533 /** 534 * @memberof Box 535 * @property {boolean} isStatic - Flag identifying that this member 536 * is accessible via the declared Class type's constructor. 537 */ 538 isStatic: { 539 enumerable: true, 540 get: function getIsStatic() { return internal.get(this).isStatic; }, 541 set: function setIsStatic(val) { 542 if (!this.isLocked) 543 internal.get(this).isStatic = val; 544 } 545 }, 546 /** 547 * @memberof Box 548 * @property {boolean} isFinal - Flag identifying that this member 549 * cannot be overridden by declarations in subclasses of the declared 550 * Class. 551 */ 552 isFinal: { 553 enumerable: true, 554 get: function getIsFinal() { return internal.get(this).isFinal; }, 555 set: function setIsFinal(val) { 556 var that = internal.get(this); 557 if (!that.isLocked) { 558 if (val) { 559 if (that.isProperty) 560 throw new ClassDefError("Isn't this self-contradicting? \"Final Property\" just as allowable as (+1 === -1). Just don't!"); 561 562 if (that.isAbstract) 563 throw new ClassDefError("Please make up your mind! Do you want to define it now(\"Final\") or later(\"Abstract\")?"); 564 } 565 566 that.isFinal = val; 567 } 568 } 569 }, 570 /** 571 * @memberof Box 572 * @property {boolean} isAbstract - Flag identifying that this member 573 * must be overridden by declarations in subclasses of the declared 574 * Class. 575 */ 576 isAbstract: { 577 enumerable: true, 578 get: function getIsAbstract() { return internal.get(this).isAbstract; }, 579 set: function setIsAbstract(val) { 580 var that = internal.get(this); 581 if (!that.isLocked) { 582 if (val) { 583 if (that.isProperty) 584 throw new ClassDefError("Have ye gone daft? How can you override a property that the owning class doesn't even define?"); 585 586 if (that.isFinal) 587 throw new ClassDefError("Please make up your mind! Do you want to define it now(\"Final\") or later(\"Abstract\")?"); 588 } 589 590 that.isAbstract = val; 591 } 592 } 593 }, 594 /** 595 * @memberof Box 596 * @property {boolean} isProperty - Flag identifying that this member 597 * uses a getter and/or setter method(s) to define its value. 598 */ 599 isProperty: { 600 enumerable: true, 601 get: function getIsProperty() { return internal.get(this).isProperty; }, 602 set: function setIsProperty(val) { 603 var that = internal.get(this); 604 if (!that.isLocked) { 605 if (val) { 606 if (that.isFinal) 607 throw new ClassDefError("Isn't this self-contradicting? \"Final Property\" just as allowable as (+1 === -1). Just don't!"); 608 609 if (that.isAbstract) 610 throw new ClassDefError("Have ye gone daft? How can you override a property that the owning class doesn't even define?"); 611 } 612 613 that.isProperty = val; 614 } 615 } 616 }, 617 /** 618 * @memberof Box 619 * @property {boolean} isDelegate - Flag identifying that this member 620 * is prepared to be used as a callback function. 621 */ 622 isDelegate: { 623 enumerable: true, 624 get: function getIsDelegate() { return internal.get(this).isDelegate; }, 625 set: function setIsDelegate(val) { 626 if (!this.isLocked) 627 internal.get(this).isDelegate = val; 628 } 629 }, 630 /** 631 * @memberof Box 632 * @property {string} type - String identifying the data type expected 633 * returned when calling, getting or setting this member. 634 */ 635 type: { 636 enumerable: true, 637 get: function getType() { return internal.get(this).type; }, 638 set: function setType(val) { 639 if (!this.isLocked) { 640 if (isValidType(val, this.value)) 641 internal.get(this).type = val; 642 else 643 throw new ClassDefError("Failed to match type '" + val +"' to value '" + this.value +"'!"); 644 } 645 } 646 }, 647 /** 648 * @memberof Box 649 * @property {Object} value - Default value of member. 650 * @readonly 651 */ 652 value: { 653 enumerable: true, 654 get: function getValue() { return internal.get(this).value; } 655 }, 656 /** 657 * Generates a string version of the values in this box for printing in 658 * a log or console. 659 * @memberof Box 660 * @function toString 661 * @returns {string} 662 * @readonly 663 */ 664 toString: { 665 enumerable: true, 666 value: function toString() { 667 var typeName = "<unknown>"; 668 if (this.type instanceof Function) { 669 if (this.type.isClass) { 670 typeName = this.type.__name; 671 } 672 else { 673 typeName = this.type.name; 674 } 675 } 676 677 var retval = { 678 isPrivate: !!this.isPrivate, 679 isProtected: !!this.isProtected, 680 isPublic: !!this.isPublic, 681 isStatic: !!this.isStatic, 682 isFinal: !!this.isFinal, 683 isProperty: !!this.isProperty, 684 isDelegate: !!this.isDelegate, 685 type: typeName, 686 value: JSON.stringify(this.value) 687 } 688 689 return JSON.stringify(retval, null, '\t'); 690 } 691 } 692 }); 693 694 Object.seal(retval); 695 return retval; 696 })(); 697 698 function modifyBox(box, params, val) { 699 box = box || new Box({ 700 privilege: Privilege.Public, 701 isFinal: false, 702 isAbstract: false, 703 isStatic: false, 704 isProperty: false, 705 isDelegate: false, 706 type: null, 707 value: val 708 }); 709 710 for (var key in params) { 711 if (params.hasOwnProperty(key) && (key in box) && key != "value") 712 box[key] = params[key]; 713 } 714 715 return box; 716 } 717 718 module.exports = (function Class() { 719 /** 720 * Class - A constructor factory designed to create functions that 721 * themselves create fully encapsulating, classical classes in JavaScript. 722 * It is not necessary to use 'new' when calling Class(...), but doing so 723 * will not interfere with how Class functions. 724 * 725 * @class Class 726 * @param {string=} name - Name of the new Class constructor function. 727 * @param {object} definition - Object describing the Class structure. 728 */ 729 function Class(name, definition) { 730 if (this instanceof Class) 731 console.warn("No need to use 'new' when declaring a new Class."); 732 733 if (arguments.length === 1) { 734 if (typeof name == "object") { 735 definition = name; 736 name = ""; 737 } 738 else { 739 throw new ClassDefError("Where's the Class definition object? At least give me {}!"); 740 } 741 } 742 743 var retval = generateTypeConstructor(name); 744 var scopes = generateScopes(retval, definition); 745 generateMetaData(retval, name, definition); 746 } 747 748 /** 749 * Private - An access modifier function. Causes val to be encapsulated as 750 * only being accessible to direct instances of the class being described. 751 * 752 * @memberof Class 753 * @function 754 * @param {*} val - A Boxed or unboxed value. 755 */ 756 function Private(val) { 757 var retval; 758 759 if (val && val.isBox) { 760 retval = modifyBox(val, { privilege: Privilege.Private }); 761 } 762 else { 763 retval = modifyBox(null, { privilege: Privilege.Private }, val); 764 } 765 766 return retval; 767 } 768 769 /** 770 * Protected - An access modifier function. Causes val to be encapsulated as 771 * only being accessible to instances of the class being described and its 772 * subclasses. 773 * 774 * @memberof Class 775 * @function 776 * @param {*} val - A Boxed or unboxed value. 777 */ 778 function Protected(val) { 779 var retval; 780 781 if (val && val.isBox) { 782 retval = modifyBox(val, { privilege: Privilege.Protected }); 783 } 784 else { 785 retval = modifyBox(null, { privilege: Privilege.Protected }, val); 786 } 787 788 return retval; 789 } 790 791 /** 792 * Public - An access modifier function. Causes val to be encapsulated as 793 * being openly accessible from the class being described. 794 * 795 * @memberof Class 796 * @function 797 * @param {*} val - A Boxed or unboxed value. 798 */ 799 function Public(val) { 800 var retval; 801 802 if (val && val.isBox) { 803 retval = modifyBox(val, { privilege: Privilege.Public }); 804 } 805 else { 806 retval = modifyBox(null, { privilege: Privilege.Public }, val); 807 } 808 809 return retval; 810 } 811 812 /** 813 * Property - An access modifier function. Requires that val is an object 814 * with a getter and/or setter method. These are the only 2 members that are 815 * honored in the object. 816 * 817 * @memberof Class 818 * @function 819 * @param {Object} val - An object defining the access methods for the 820 * property. Must contain at leaset one of the following properties. 821 * @prop {function=} val.get - A function that calculates or retrieves the 822 * desired value. Takes no parameters. 823 * @prop {function=} val.set - A function that performs the needed actions 824 * to ensure that val.get returns the desired value. Takes a single 825 * parameter. 826 */ 827 function Property(val) { 828 var retval; 829 830 if (val && val.isBox) { 831 retval = modifyBox(val, { isProperty: true }); 832 } 833 else { 834 retval = modifyBox(null, { isProperty: true }, val); 835 } 836 837 return retval; 838 } 839 840 /** 841 * Static - An access modifier function. Causes val to be encapsulated as 842 * data owned by the constructor function and not the class's prototype. 843 * 844 * @memberof Class 845 * @function 846 * @param {*} val - A Boxed or unboxed value. 847 */ 848 function Static(val) { 849 var retval; 850 851 if (val && val.isBox) { 852 retval = modifyBox(val, { isStatic: true }); 853 } 854 else { 855 retval = modifyBox(null, { isStatic: true }, val); 856 } 857 858 return retval; 859 } 860 861 /** 862 * Final - An access modifier function. Causes val to be encapsulated as 863 * immutable. If val is a function, it is automatically Final. 864 * 865 * @memberof Class 866 * @function 867 * @param {*} val - A Boxed or unboxed value. 868 */ 869 function Final(val) { 870 var retval; 871 872 if (val && val.isBox) { 873 retval = modifyBox(val, { isFinal: true }); 874 } 875 else { 876 retval = modifyBox(null, { isFinal: true }, val); 877 } 878 879 return retval; 880 } 881 882 /** 883 * Abstract - An access modifier function. Causes val to be encapsulated as 884 * an undefined method. The parameter val must reference a function. 885 * 886 * @memberof Class 887 * @function 888 * @param {function} val - A Boxed or unboxed function. 889 */ 890 function Abstract(val) { 891 var retval; 892 893 if (val && val.isBox) { 894 retval = modifyBox(val, { isAbstract: true }); 895 } 896 else { 897 retval = modifyBox(null, { isAbstract: true }, val); 898 } 899 900 return retval; 901 } 902 903 /** 904 * Delegate - An access modifier function. Causes val to be encapsulated as 905 * bound method. Attempts to call this method, even after assignment to a 906 * variable, will always use the owning Class instance or type definition 907 * (if Static) as its scope. The parameter val must reference a function. 908 * 909 * @memberof Class 910 * @function 911 * @param {function} val - A Boxed or unboxed function. 912 */ 913 function Delegate(val) { 914 var retval; 915 916 if (val && val.isBox) { 917 retval = modifyBox(val, { isDelegate: true }); 918 } 919 else { 920 retval = modifyBox(null, { isDelegate: true }, val); 921 } 922 923 return retval; 924 } 925 926 /** 927 * Abstract - An access modifier function. Causes val to be tested to see if 928 * it matches the given type at definition time and at runtime, both on 929 * input and output. 930 * 931 * @memberof Class 932 * @function 933 * @param {string|Class} type - A type name string or Class type constructor. 934 * @param {*} val - A Boxed or unboxed value. 935 */ 936 function Type(type, val) { 937 var retval = null; 938 939 if (type && (type.isClass || type.isInterface || (type === Function) || 940 (type === String) || (type === Date) || 941 ((typeof type == "string") && 942 ((type.toLowerCase() === "string") || 943 (type.toLowerCase() === "number") || 944 (type.toLowerCase() === "boolean") || 945 (type.toLowerCase() === "symbol"))))) { 946 if (val instanceof Box) { 947 if (val.isProperty || isSimpleFunction(val.value) || isValid(type, val.value)) { 948 retval = modifyBox(val, { type: type }); 949 } 950 else 951 throw new TypeError("Expected value of type '" + type.__name + "'. Found " + val.value); 952 } 953 else if (isValid(type, val)) { 954 retval = modifyBox(null, { type: type }, val); 955 } 956 else 957 throw new TypeError("Expected value of type '" + type.__name + "'. Found " + val); 958 } 959 else 960 throw new TypeError("Type must reference either a Class, an Interface, or an intrinsic type!"); 961 962 return retval; 963 } 964 965 function Initialize(_global) { 966 Object.defineProperties(_global, { 967 Private: { 968 enumerable: true, 969 value: Private 970 }, 971 Protected: { 972 enumerable: true, 973 value: Protected 974 }, 975 Public: { 976 enumerable: true, 977 value: Public 978 }, 979 Property: { 980 enumerable: true, 981 value: Property 982 }, 983 Static: { 984 enumerable: true, 985 value: Static 986 }, 987 Final: { 988 enumerable: true, 989 value: Final 990 }, 991 Abstract: { 992 enumerable: true, 993 value: Abstract 994 }, 995 Delegate: { 996 enumerable: true, 997 value: Delegate 998 }, 999 Type: { 1000 enumerable: true, 1001 value: Type 1002 }, 1003 Modes: { 1004 enumerable: true, 1005 value: ClassModes 1006 } 1007 }); 1008 } 1009 1010 Initialize(Class); 1011 Object.defineProperties(Class, { 1012 Initialize: { 1013 enumerable: true, 1014 value: Initialize 1015 } 1016 }); 1017 1018 Object.freeze(Class); 1019 return Class; 1020 })(); 1021