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