1 /*
  2  *  Copyright © 2009 Apple Inc. All rights reserved.
  3  */
  4 
  5 /**
  6  *  @class
  7  *
  8  *  <p>The TKClass function will process a constructor function and set up its inheritance chain, synthetize
  9  *  a number of its instance properties and mix in additional capabilities based properties defined on
 10  *  that function. The supported properties are:</p>
 11  *
 12  *  <ul>
 13  *    <li><code>inherits</code> – the superclass</li>
 14  *    <li><code>includes</code> – an Array of modules to pull in</li>
 15  *    <li><code>synthetizes</code> – an Array of properties to synthetize</li>
 16  *  </ul>
 17  *
 18  *  <p>For instance, consider the following class declaration:</p>
 19  *
 20  *  <pre class="example">
 21 // extends MySuperClass
 22 MyClass.inherits = MySuperClass;
 23 
 24 // mixes in the TKEventTriage and TKPropertyTriage modules
 25 MyClass.includes = [TKEventTriage, TKPropertyTriage]; 
 26 
 27 // synthetizes the .foo and .bar properties
 28 MyClass.synthetizes = ['foo', 'bar'];
 29 
 30 function MyClass () {
 31   // constructor code goes here
 32 };
 33 
 34 // process properties set up on the MyClass constructor
 35 TKClass(MyClass);
 36  *  </pre>
 37  *
 38  *  <p>Synthetization is fully automated as long as the class that wishes to offer synthetized properties follows
 39  *  the following rules:</p>
 40  *
 41  *  <ul>
 42  *    <li>say the class wishes to synthetize the property <code>.foo</code></li>
 43  *    <li>if the class chooses to define a getter method, it must be defined as <code>getFoo</code> on its <code>prototype</code>
 44  *    <li>if the class chooses to define a setter method, it must be defined as <code>setFoo</code> on its <code>prototype</code>
 45  *    <li>the class must define an instance variable <code>._foo</code> which is the internal object that the class is responsible to update if either a setter is specified</li>
 46  *  </ul>
 47  *
 48  *  <p>So our previous class declaration could be extended as follows:</p>
 49  *
 50  *  <pre class="example">
 51 function MyClass () {
 52   this._foo = '';
 53 };
 54 
 55 // custom setter for .foo, the getter is not specified
 56 // and TKClass() will automatically create it
 57 MyClass.prototype.setFoo = function (newFooValue) {
 58   this._foo = newFooValue;
 59 };
 60 
 61 // custom getter for .foo, the setter is not specified
 62 // and TKClass() will automatically create it
 63 MyClass.prototype.getBar = function (newFooValue) {
 64   return 'Hello ' + this._foo;
 65 };
 66  *  </pre>
 67  *
 68  *  @since TuneKit 1.0
 69  *
 70  *  @constructor
 71  *  @param theClass {Object} The class.
 72  */
 73 
 74 function TKClass (theClass) {
 75   // check out if we have inheritance set up, otherwise, make TKObject the default superclass
 76   if (TKUtils.objectIsUndefined(theClass.inherits) && theClass !== TKObject) {
 77     theClass.inherits = TKObject;
 78   }
 79   
 80   // check out if we have mixins
 81   if (theClass.includes) {
 82     TKClass.mixin(theClass.prototype, theClass.includes);
 83   }
 84 
 85   // synthetizes properties defined locally
 86   var properties = (theClass.synthetizes) ? theClass.synthetizes : [];
 87   // now synthetize
 88   for (var i = 0; i < properties.length; i++) {
 89     TKClass.synthetizeProperty(theClass.prototype, properties[i], true);
 90   }
 91 
 92   // synthetizes properties by compiling them up the inheritance chain
 93   var aClass = theClass;
 94   var properties = [];
 95   while (aClass.inherits) {
 96     aClass = aClass.inherits;
 97     if (aClass.synthetizes) {
 98       properties = aClass.synthetizes.concat(properties);
 99     }            
100   }
101   // now synthetize
102   for (var i = 0; i < properties.length; i++) {
103     TKClass.synthetizeProperty(theClass.prototype, properties[i], false);
104   }
105 
106   // go through each method and save its name as a custom property
107   // that we'll use later in TKObject.callSuper()
108   for (var i in theClass.prototype) {
109     // make sure we don't touch properties that were synthetized
110     if (theClass.prototype.__lookupGetter__(i)) {
111       continue;
112     }
113     var prop = theClass.prototype[i];
114     if (TKUtils.objectIsFunction(prop)) {
115       prop._class = theClass;
116       prop._name = i;
117       prop.displayName = TKUtils.createDisplayName(theClass.name, i);
118     }
119   }
120 
121   // inherit from the superclass
122   // default to TKObject if nothing is specified
123   if (theClass !== TKObject) {
124     theClass.prototype.__proto__ = theClass.inherits.prototype;
125   }
126 };
127 
128 TKClass.synthetizeProperty = function (object, propertyName, isPropertyInherited) {
129   var camel_ready = propertyName.charAt(0).toUpperCase() + propertyName.substr(1);
130   var getter_name = 'get' + camel_ready;
131   var setter_name = 'set' + camel_ready;
132   // check on function availability
133   var has_getter = TKUtils.objectHasMethod(object, getter_name);
134   var has_setter = TKUtils.objectHasMethod(object, setter_name);
135   
136   // if we have neither a getter or a setter, then do nothing
137   // unless the property is defined locally
138   if (!isPropertyInherited && !(has_getter || has_setter)) {
139     return;
140   }
141   
142   // assign the setter function if we have one
143   if (has_setter) {
144     var specified_setter_function = function (newValue) {
145       object[setter_name].call(this, newValue);
146       this.notifyPropertyChange(propertyName);
147     };
148     specified_setter_function.displayName = 'Specified setter for .' + propertyName + ' on ' + object.constructor.name;
149     object.__defineSetter__(propertyName, specified_setter_function);
150   }
151   // otherwise just assign to _propertyName
152   else {
153     var default_setter_function = function (newValue) {
154       this['_' + propertyName] = newValue;
155       this.notifyPropertyChange(propertyName);
156     };
157     default_setter_function.displayName = 'Default setter for .' + propertyName + ' on ' + object.constructor.name;
158     object.__defineSetter__(propertyName, default_setter_function);
159   }
160   
161   // assign the getter function if we have one
162   if (has_getter) {
163     object.__defineGetter__(propertyName, object[getter_name]);
164   }
165   // otherwise just return _propertyName
166   else {
167     var default_getter_function = function () {
168       return this['_' + propertyName];
169     };
170     default_getter_function.displayName = 'Default getter for .' + propertyName + ' on ' + object.constructor.name;
171     object.__defineGetter__(propertyName, default_getter_function);
172   }
173 };
174 
175 TKClass.mixin = function (target, sources) {
176   for (var i = 0; i < sources.length; i++) {
177     TKUtils.copyPropertiesFromSourceToTarget(sources[i], target);
178   }
179 };
180 
181 TKUtils.setupDisplayNames(TKClass, 'TKClass');
182