1 /* 2 * Filename: Interface.js 3 * Created By: Ranando D. King 4 * License: Apache 2.0 5 * 6 * Copyright 2014 Ranando D. King 7 * 8 * Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 /* 22 Interface Definition Object: 23 { 24 Extends: [ 25 interface1, 26 interface2, ... 27 ], 28 Properties: { 29 name: writable, 30 name: writable, ... 31 }, 32 Methods: { 33 name: paramCnt1, 34 name: paramCnt2, ... 35 } 36 } 37 */ 38 39 var WeakMap = require("./WeakMap"); 40 41 var Interface = (function() { 42 var scope = new WeakMap(); 43 44 var Extend = function Extend(def, obj) { 45 if (obj.hasOwnProperty("Extends") && Array.isArray(obj.Extends)) { 46 if (!def.hasOwnProperty("Extends")) 47 def.Extends = []; 48 49 for (var index in obj.Extends) { 50 if (obj.Extends.hasOwnProperty(index)) { 51 var parent = obj.Extends[index]; 52 if (def.Extends.indexOf(parent) < 0) 53 def.Extends.push(parent); 54 } 55 } 56 } 57 58 if (obj.hasOwnProperty("Properties")) { 59 for (var property in obj.Properties) { 60 if (obj.Properties.hasOwnProperty(property)) { 61 var prop = obj.Properties[property]; 62 63 if (!def.hasOwnProperty("Properties")) 64 def.Properties = {}; 65 66 if (def.Properties.hasOwnProperty(property)) { 67 if (def.Properties[property] != prop) 68 throw new TypeError("Conflicting definitions for property '" + property + "'!"); 69 } 70 else 71 def.Properties[property] = prop; 72 } 73 } 74 } 75 76 if (obj.hasOwnProperty("Methods")) { 77 for (var method in obj.Methods) { 78 if (obj.Methods.hasOwnProperty(method)) { 79 var m = obj.Methods[method]; 80 81 if (!def.hasOwnProperty("Methods")) 82 def.Methods = {}; 83 84 if (def.Methods.hasOwnProperty(method)) { 85 if (def.Methods[method] != m) 86 throw new TypeError("Conflicting definitions for method '" + method + "'!"); 87 } 88 else 89 def.Methods[method] = m; 90 } 91 } 92 } 93 }; 94 95 var Inherit = function Inherit(def, obj) { 96 if (obj instanceof Interface) { 97 Extend(def, obj); 98 } 99 else 100 throw new TypeError("Interfaces can only inherit from Interfaces!"); 101 }; 102 103 var Flatten = function Flatten(def) { 104 var retval = {}; 105 106 Extend(retval, def); 107 108 if (Array.isArray(def.Extends)) { 109 var count = def.Extends.length; 110 for (var i=0; i<count; ++i) 111 Inherit(retval, def.Extends[i]); 112 } 113 else if (def.Extends) 114 throw new SyntaxError("Extends must be an array of Interface objects if defined!"); 115 116 Object.freeze(retval); 117 return retval; 118 }; 119 120 var $$ = function Interface(name, definition) { 121 //If we were only supplied 1 argument, and it's not a string, just assume it's the scope. 122 if ((arguments.length === 1) && (typeof name !== "string")) { 123 definition = name; 124 name = ""; 125 } 126 127 //Yes. Empty classes are valid! 128 definition = definition || {}; 129 //Yes. Anonymous classes are also valid! 130 name = name || ""; 131 132 Object.freeze(definition); 133 scope.set(this, { raw: definition, flattened: Flatten(definition)}); 134 135 Object.freeze(this); 136 return this; 137 }; 138 139 Object.defineProperties($$.prototype, { 140 valueOf: { 141 get: function getValueOf() { return JSON.stringify(scope.get(this).raw); } 142 }, 143 Properties: { 144 enumerable: true, 145 get: function getProperties() { return scope.get(this).raw.Properties; } 146 }, 147 Methods: { 148 enumerable: true, 149 get: function getMethods() { return scope.get(this).raw.Methods; } 150 }, 151 Extends: { 152 enumerable: true, 153 get: function getExtends() { return scope.get(this).raw.Extends; } 154 }, 155 isInterface: { 156 enumerable: true, 157 value: true 158 }, 159 isImplementedBy: { 160 enumerable: true, 161 value: function isImplementedBy(obj) { 162 var retval = true; 163 164 if (!obj) 165 throw new TypeError("The obj potentially implementing this interface cannot be null!"); 166 167 var test = function test(obj, def, isProp) { 168 var rval = true; 169 for (var member in def) { 170 var test = false; 171 if (def.hasOwnProperty(member)) { 172 console.log("Testing for '" + member + "'..."); 173 if (obj.hasOwnProperty(member)) { 174 var box = obj[member]; 175 if (box) { 176 console.log("'"+ member + "' has a value..."); 177 if (box.isBox) { 178 console.log("'"+ member + "' is Boxed..."); 179 if (box.isPublic) { 180 console.log("'"+ member + "' is Public..."); 181 if (isProp && box.isProperty) { 182 console.log("'"+ member + "' is a Property..."); 183 if (def[member]) { 184 if (box.value && (box.value.set instanceof Function)) 185 test |= true; 186 } 187 if (!def[member] || test) { 188 if (box.value && (box.value.get instanceof Function)) 189 test |= true; 190 } 191 192 } 193 else if (!isProp && !box.isProperty) { 194 console.log("'"+ member + "' is a Method..."); 195 if (box.value && (box.value instanceof Function)) { 196 if (box.value.length == def[member]) 197 test |= true; 198 } 199 } 200 } 201 } 202 else if ((box instanceof Function) == !isProp) { 203 console.log("'"+ member + "' is not Boxed..."); 204 if (!isProp) { 205 console.log("'"+ member + "' is a Method..."); 206 test |= (def[member] === box.length); 207 } 208 else 209 test |= true; 210 } 211 } 212 else 213 test |= isProp; 214 } 215 } 216 217 console.log("'"+ member + "' does" + (!test? " not": "") + " exist!"); 218 rval &= test; 219 if (!retval) break; 220 } 221 222 return rval; 223 }; 224 225 if (obj.isClass || obj.isClassInstance) 226 retval = obj.Implements(this); 227 else { 228 var container = scope.get(this).flattened; 229 if (this.hasOwnProperty("Properties")) 230 retval &= test(obj, container.Properties, true); 231 232 if (this.hasOwnProperty("Methods")) 233 retval &= test(obj, container.Methods, false); 234 } 235 236 return retval; 237 } 238 }, 239 inheritsFrom: { 240 enumerable: true, 241 value: function inheritsFrom(obj) { 242 if (!obj) 243 throw new SyntaxError("The object being tested as an ancestor cannot be null!"); 244 245 var definition = scope.get(this).raw; 246 var retval = !definition.Extends || (definition.Extends.indexOf(obj) == -1); 247 return retval; 248 } 249 } 250 }); 251 252 Object.seal($$); 253 return $$; 254 })(); 255 256 //If some form of require.js exists, export the class 257 if (module && exports && module.exports === exports) 258 module.exports = Interface; 259