1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/deprecated/traits/core.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,322 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +module.metadata = { 1.11 + "stability": "deprecated" 1.12 +}; 1.13 + 1.14 +// Design inspired by: http://www.traitsjs.org/ 1.15 + 1.16 +// shortcuts 1.17 +const getOwnPropertyNames = Object.getOwnPropertyNames, 1.18 + getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, 1.19 + hasOwn = Object.prototype.hasOwnProperty, 1.20 + _create = Object.create; 1.21 + 1.22 +function doPropertiesMatch(object1, object2, name) { 1.23 + // If `object1` has property with the given `name` 1.24 + return name in object1 ? 1.25 + // then `object2` should have it with the same value. 1.26 + name in object2 && object1[name] === object2[name] : 1.27 + // otherwise `object2` should not have property with the given `name`. 1.28 + !(name in object2); 1.29 +} 1.30 + 1.31 +/** 1.32 + * Compares two trait custom property descriptors if they are the same. If 1.33 + * both are `conflict` or all the properties of descriptor are equal returned 1.34 + * value will be `true`, otherwise it will be `false`. 1.35 + * @param {Object} desc1 1.36 + * @param {Object} desc2 1.37 + */ 1.38 +function areSame(desc1, desc2) { 1.39 + return ('conflict' in desc1 && desc1.conflict && 1.40 + 'conflict' in desc2 && desc2.conflict) || 1.41 + (doPropertiesMatch(desc1, desc2, 'get') && 1.42 + doPropertiesMatch(desc1, desc2, 'set') && 1.43 + doPropertiesMatch(desc1, desc2, 'value') && 1.44 + doPropertiesMatch(desc1, desc2, 'enumerable') && 1.45 + doPropertiesMatch(desc1, desc2, 'required') && 1.46 + doPropertiesMatch(desc1, desc2, 'conflict')); 1.47 +} 1.48 + 1.49 +/** 1.50 + * Converts array to an object whose own property names represent 1.51 + * values of array. 1.52 + * @param {String[]} names 1.53 + * @returns {Object} 1.54 + * @example 1.55 + * Map(['foo', ...]) => { foo: true, ...} 1.56 + */ 1.57 +function Map(names) { 1.58 + let map = {}; 1.59 + for each (let name in names) 1.60 + map[name] = true; 1.61 + return map; 1.62 +} 1.63 + 1.64 + 1.65 +const ERR_CONFLICT = 'Remaining conflicting property: ', 1.66 + ERR_REQUIRED = 'Missing required property: '; 1.67 +/** 1.68 + * Constant singleton, representing placeholder for required properties. 1.69 + * @type {Object} 1.70 + */ 1.71 +const required = { toString: function()'<Trait.required>' }; 1.72 +exports.required = required; 1.73 + 1.74 +/** 1.75 + * Generates custom **required** property descriptor. Descriptor contains 1.76 + * non-standard property `required` that is equal to `true`. 1.77 + * @param {String} name 1.78 + * property name to generate descriptor for. 1.79 + * @returns {Object} 1.80 + * custom property descriptor 1.81 + */ 1.82 +function Required(name) { 1.83 + function required() { throw new Error(ERR_REQUIRED + name) } 1.84 + return { 1.85 + get: required, 1.86 + set: required, 1.87 + required: true 1.88 + }; 1.89 +} 1.90 + 1.91 +/** 1.92 + * Generates custom **conflicting** property descriptor. Descriptor contains 1.93 + * non-standard property `conflict` that is equal to `true`. 1.94 + * @param {String} name 1.95 + * property name to generate descriptor for. 1.96 + * @returns {Object} 1.97 + * custom property descriptor 1.98 + */ 1.99 +function Conflict(name) { 1.100 + function conflict() { throw new Error(ERR_CONFLICT + name) } 1.101 + return { 1.102 + get: conflict, 1.103 + set: conflict, 1.104 + conflict: true 1.105 + }; 1.106 +} 1.107 + 1.108 +/** 1.109 + * Function generates custom properties descriptor of the `object`s own 1.110 + * properties. All the inherited properties are going to be ignored. 1.111 + * Properties with values matching `required` singleton will be marked as 1.112 + * 'required' properties. 1.113 + * @param {Object} object 1.114 + * Set of properties to generate trait from. 1.115 + * @returns {Object} 1.116 + * Properties descriptor of all of the `object`'s own properties. 1.117 + */ 1.118 +function trait(properties) { 1.119 + let result = {}, 1.120 + keys = getOwnPropertyNames(properties); 1.121 + for each (let key in keys) { 1.122 + let descriptor = getOwnPropertyDescriptor(properties, key); 1.123 + result[key] = (required === descriptor.value) ? Required(key) : descriptor; 1.124 + } 1.125 + return result; 1.126 +} 1.127 +exports.Trait = exports.trait = trait; 1.128 + 1.129 +/** 1.130 + * Composes new trait. If two or more traits have own properties with the 1.131 + * same name, the new trait will contain a 'conflict' property for that name. 1.132 + * 'compose' is a commutative and associative operation, and the order of its 1.133 + * arguments is not significant. 1.134 + * 1.135 + * @params {Object} trait 1.136 + * Takes traits as an arguments 1.137 + * @returns {Object} 1.138 + * New trait containing the combined own properties of all the traits. 1.139 + * @example 1.140 + * var newTrait = compose(trait_1, trait_2, ..., trait_N); 1.141 + */ 1.142 +function compose(trait1, trait2) { 1.143 + let traits = Array.slice(arguments, 0), 1.144 + result = {}; 1.145 + for each (let trait in traits) { 1.146 + let keys = getOwnPropertyNames(trait); 1.147 + for each (let key in keys) { 1.148 + let descriptor = trait[key]; 1.149 + // if property already exists and it's not a requirement 1.150 + if (hasOwn.call(result, key) && !result[key].required) { 1.151 + if (descriptor.required) 1.152 + continue; 1.153 + if (!areSame(descriptor, result[key])) 1.154 + result[key] = Conflict(key); 1.155 + } else { 1.156 + result[key] = descriptor; 1.157 + } 1.158 + } 1.159 + } 1.160 + return result; 1.161 +} 1.162 +exports.compose = compose; 1.163 + 1.164 +/** 1.165 + * Composes new trait with the same own properties as the original trait, 1.166 + * except that all property names appearing in the first argument are replaced 1.167 + * by 'required' property descriptors. 1.168 + * @param {String[]} keys 1.169 + * Array of strings property names. 1.170 + * @param {Object} trait 1.171 + * A trait some properties of which should be excluded. 1.172 + * @returns {Object} 1.173 + * @example 1.174 + * var newTrait = exclude(['name', ...], trait) 1.175 + */ 1.176 +function exclude(keys, trait) { 1.177 + let exclusions = Map(keys), 1.178 + result = {}; 1.179 + 1.180 + keys = getOwnPropertyNames(trait); 1.181 + 1.182 + for each (let key in keys) { 1.183 + if (!hasOwn.call(exclusions, key) || trait[key].required) 1.184 + result[key] = trait[key]; 1.185 + else 1.186 + result[key] = Required(key); 1.187 + } 1.188 + return result; 1.189 +} 1.190 + 1.191 +/** 1.192 + * Composes a new trait with all of the combined properties of the argument 1.193 + * traits. In contrast to `compose`, `override` immediately resolves all 1.194 + * conflicts resulting from this composition by overriding the properties of 1.195 + * later traits. Trait priority is from left to right. I.e. the properties of 1.196 + * the leftmost trait are never overridden. 1.197 + * @params {Object} trait 1.198 + * @returns {Object} 1.199 + * @examples 1.200 + * // override is associative: 1.201 + * override(t1,t2,t3) 1.202 + * // is equivalent to 1.203 + * override(t1, override(t2, t3)) 1.204 + * // or 1.205 + * to override(override(t1, t2), t3) 1.206 + * 1.207 + * // override is not commutative: 1.208 + * override(t1,t2) 1.209 + * // is not equivalent to 1.210 + * override(t2,t1) 1.211 + */ 1.212 +function override() { 1.213 + let traits = Array.slice(arguments, 0), 1.214 + result = {}; 1.215 + for each (let trait in traits) { 1.216 + let keys = getOwnPropertyNames(trait); 1.217 + for each(let key in keys) { 1.218 + let descriptor = trait[key]; 1.219 + if (!hasOwn.call(result, key) || result[key].required) 1.220 + result[key] = descriptor; 1.221 + } 1.222 + } 1.223 + return result; 1.224 +} 1.225 +exports.override = override; 1.226 + 1.227 +/** 1.228 + * Composes a new trait with the same properties as the original trait, except 1.229 + * that all properties whose name is an own property of map will be renamed to 1.230 + * map[name], and a 'required' property for name will be added instead. 1.231 + * @param {Object} map 1.232 + * An object whose own properties serve as a mapping from old names to new 1.233 + * names. 1.234 + * @param {Object} trait 1.235 + * A trait object 1.236 + * @returns {Object} 1.237 + * @example 1.238 + * var newTrait = rename(map, trait); 1.239 + */ 1.240 +function rename(map, trait) { 1.241 + let result = {}, 1.242 + keys = getOwnPropertyNames(trait); 1.243 + for each(let key in keys) { 1.244 + // must be renamed & it's not requirement 1.245 + if (hasOwn.call(map, key) && !trait[key].required) { 1.246 + let alias = map[key]; 1.247 + if (hasOwn.call(result, alias) && !result[alias].required) 1.248 + result[alias] = Conflict(alias); 1.249 + else 1.250 + result[alias] = trait[key]; 1.251 + if (!hasOwn.call(result, key)) 1.252 + result[key] = Required(key); 1.253 + } else { // must not be renamed or its a requirement 1.254 + // property is not in result trait yet 1.255 + if (!hasOwn.call(result, key)) 1.256 + result[key] = trait[key]; 1.257 + // property is already in resulted trait & it's not requirement 1.258 + else if (!trait[key].required) 1.259 + result[key] = Conflict(key); 1.260 + } 1.261 + } 1.262 + return result; 1.263 +} 1.264 + 1.265 +/** 1.266 +* Composes new resolved trait, with all the same properties as the original 1.267 +* trait, except that all properties whose name is an own property of 1.268 +* resolutions will be renamed to `resolutions[name]`. If it is 1.269 +* `resolutions[name]` is `null` value is changed into a required property 1.270 +* descriptor. 1.271 +* function can be implemented as `rename(map,exclude(exclusions, trait))` 1.272 +* where map is the subset of mappings from oldName to newName and exclusions 1.273 +* is an array of all the keys that map to `null`. 1.274 +* Note: it's important to **first** `exclude`, **then** `rename`, since 1.275 +* `exclude` and rename are not associative. 1.276 +* @param {Object} resolutions 1.277 +* An object whose own properties serve as a mapping from old names to new 1.278 +* names, or to `null` if the property should be excluded. 1.279 +* @param {Object} trait 1.280 +* A trait object 1.281 +* @returns {Object} 1.282 +* Resolved trait with the same own properties as the original trait. 1.283 +*/ 1.284 +function resolve(resolutions, trait) { 1.285 + let renames = {}, 1.286 + exclusions = [], 1.287 + keys = getOwnPropertyNames(resolutions); 1.288 + for each (let key in keys) { // pre-process renamed and excluded properties 1.289 + if (resolutions[key]) // old name -> new name 1.290 + renames[key] = resolutions[key]; 1.291 + else // name -> undefined 1.292 + exclusions.push(key); 1.293 + } 1.294 + return rename(renames, exclude(exclusions, trait)); 1.295 +} 1.296 +exports.resolve = resolve; 1.297 + 1.298 +/** 1.299 + * `create` is like `Object.create`, except that it ensures that: 1.300 + * - an exception is thrown if 'trait' still contains required properties 1.301 + * - an exception is thrown if 'trait' still contains conflicting 1.302 + * properties 1.303 + * @param {Object} 1.304 + * prototype of the completed object 1.305 + * @param {Object} trait 1.306 + * trait object to be turned into a complete object 1.307 + * @returns {Object} 1.308 + * An object with all of the properties described by the trait. 1.309 + */ 1.310 +function create(proto, trait) { 1.311 + let properties = {}, 1.312 + keys = getOwnPropertyNames(trait); 1.313 + for each(let key in keys) { 1.314 + let descriptor = trait[key]; 1.315 + if (descriptor.required && !hasOwn.call(proto, key)) 1.316 + throw new Error(ERR_REQUIRED + key); 1.317 + else if (descriptor.conflict) 1.318 + throw new Error(ERR_CONFLICT + key); 1.319 + else 1.320 + properties[key] = descriptor; 1.321 + } 1.322 + return _create(proto, properties); 1.323 +} 1.324 +exports.create = create; 1.325 +