1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * Create Example Class:
  9  * <pre>
 10  * var Bird = Hilo.Class.create({
 11  *     Extends: Animal,
 12  *     Mixes: EventMixin,
 13  *     constructor: function(name){
 14  *         this.name = name;
 15  *     },
 16  *     fly: function(){
 17  *         console.log('I am flying');
 18  *     },
 19  *     Statics: {
 20  *         isBird: function(bird){
 21  *             return bird instanceof Bird;
 22  *         }
 23  *     }
 24  * });
 25  *
 26  * var swallow = new Bird('swallow');
 27  * swallow.fly();
 28  * Bird.isBird(swallow);
 29  * </pre>
 30  * @namespace Class Class is created to aid the developer.
 31  * @static
 32  * @module hilo/core/Class
 33  */
 34 var Class = (function(){
 35 
 36 /**
 37  * Create a class based on the parameters, properties and methods specified.
 38  * @param {Object} properties Properties and methods to create the class.
 39  * <ul>
 40  * <li><b>Extends</b> - Designed to inherit the parent class.</li>
 41  * <li><b>Mixes</b> - Specifies mixed member collection object.</li>
 42  * <li><b>Statics</b> - Static property or method specified class.</li>
 43  * <li><b>constructor</b> -  The constructor of specified class.</li>
 44  * <li>Other members of the property or method to create the class.</li>
 45  * </ul>
 46  * @returns {Object} Create classes.
 47  */
 48 var create = function(properties){
 49     properties = properties || {};
 50     var clazz = properties.hasOwnProperty('constructor') ? properties.constructor : function(){};
 51     implement.call(clazz, properties);
 52     return clazz;
 53 };
 54 
 55 /**
 56  * @private
 57  */
 58 var implement = function(properties){
 59     var proto = {}, key, value;
 60     for(key in properties){
 61         value = properties[key];
 62         if(classMutators.hasOwnProperty(key)){
 63             classMutators[key].call(this, value);
 64         }else{
 65             proto[key] = value;
 66         }
 67     }
 68 
 69     mix(this.prototype, proto);
 70 };
 71 
 72 var classMutators = /** @ignore */{
 73     Extends: function(parent){
 74         var existed = this.prototype, proto = createProto(parent.prototype);
 75         //inherit static properites
 76         mix(this, parent);
 77         //keep existed properties
 78         mix(proto, existed);
 79         //correct constructor
 80         proto.constructor = this;
 81         //prototype chaining
 82         this.prototype = proto;
 83         //shortcut to parent's prototype
 84         this.superclass = parent.prototype;
 85     },
 86 
 87     Mixes: function(items){
 88         items instanceof Array || (items = [items]);
 89         var proto = this.prototype, item;
 90 
 91         while(item = items.shift()){
 92             mix(proto, item.prototype || item);
 93         }
 94     },
 95 
 96     Statics: function(properties){
 97         mix(this, properties);
 98     }
 99 };
100 
101 /**
102  * @private
103  */
104 var createProto = (function(){
105     if(Object.__proto__){
106         return function(proto){
107             return {__proto__: proto};
108         };
109     }else{
110         var Ctor = function(){};
111         return function(proto){
112             Ctor.prototype = proto;
113             return new Ctor();
114         };
115     }
116 })();
117 
118 /**
119  * Mixed property or method.
120  * @param {Object} target Mixed audiences.
121  * @param {Object} source The source whose methods and properties are to be mixed. It can support multiple source parameters.
122  * @returns {Object} Mixed audiences.
123  */
124 var mix = function(target){
125     for(var i = 1, len = arguments.length; i < len; i++){
126         var source  = arguments[i], defineProps;
127         for(var key in source){
128             var prop = source[key];
129             if(prop && typeof prop === 'object'){
130                 if(prop.value !== undefined || typeof prop.get === 'function' || typeof prop.set === 'function'){
131                     defineProps = defineProps || {};
132                     defineProps[key] = prop;
133                     continue;
134                 }
135             }
136             target[key] = prop;
137         }
138         if(defineProps) defineProperties(target, defineProps);
139     }
140 
141     return target;
142 };
143 
144 var defineProperty, defineProperties;
145 try{
146     defineProperty = Object.defineProperty;
147     defineProperties = Object.defineProperties;
148     defineProperty({}, '$', {value:0});
149 }catch(e){
150     if('__defineGetter__' in Object){
151         defineProperty = function(obj, prop, desc){
152             if('value' in desc) obj[prop] = desc.value;
153             if('get' in desc) obj.__defineGetter__(prop, desc.get);
154             if('set' in desc) obj.__defineSetter__(prop, desc.set);
155             return obj;
156         };
157         defineProperties = function(obj, props){
158             for(var prop in props){
159                 if(props.hasOwnProperty(prop)){
160                     defineProperty(obj, prop, props[prop]);
161                 }
162             }
163             return obj;
164         };
165     }
166 }
167 
168 return {create:create, mix:mix};
169 
170 })();
171