1 var Proxy = require("node-proxy"), object = require("./object"), merge = object.merge, functions = require("./functions"), misc = require("./misc");
  2 
  3 var comb = exports;
  4 var handlerMaker = function (obj) {
  5     return {
  6         getOwnPropertyDescriptor:function (name) {
  7             var desc = Object.getOwnPropertyDescriptor(obj, name);
  8             // a trapping proxy's properties must always be configurable
  9             if (desc !== undefined) {
 10                 desc.configurable = true;
 11             }
 12             return desc;
 13         },
 14         getPropertyDescriptor:function (name) {
 15             var desc = Object.getPropertyDescriptor(obj, name); // not in ES5
 16             // a trapping proxy's properties must always be configurable
 17             if (desc !== undefined) {
 18                 desc.configurable = true;
 19             }
 20             return desc;
 21         },
 22         getOwnPropertyNames:function () {
 23             return Object.getOwnPropertyNames(obj);
 24         },
 25         getPropertyNames:function () {
 26             return Object.getPropertyNames(obj);                // not in ES5
 27         },
 28         defineProperty:function (name, desc) {
 29             Object.defineProperty(obj, name, desc);
 30         },
 31         delete:function (name) {
 32             return delete obj[name];
 33         },
 34         fix:function () {
 35             if (Object.isFrozen(obj)) {
 36                 var result = {};
 37                 Object.getOwnPropertyNames(obj).forEach(function (name) {
 38                     result[name] = Object.getOwnPropertyDescriptor(obj, name);
 39                 });
 40                 return result;
 41             }
 42             // As long as obj is not frozen, the proxy won't allow itself to be fixed
 43             return undefined; // will cause a TypeError to be thrown
 44         },
 45 
 46         has:function (name) {
 47             return name in obj;
 48         },
 49         hasOwn:function (name) {
 50             return ({}).hasOwnProperty.call(obj, name);
 51         },
 52         get:function (receiver, name) {
 53             return obj[name];
 54         },
 55         set:function (receiver, name, val) {
 56             obj[name] = val;
 57             return true;
 58         }, // bad behavior when set fails in non-strict mode
 59         enumerate:function () {
 60             var result = [];
 61             for (var name in obj) {
 62                 result.push(name);
 63             }
 64             return result;
 65         },
 66         keys:function () {
 67             return Object.keys(obj);
 68         }
 69 
 70     };
 71 };
 72 
 73 var noSuchMethodHandler = function (obj, handler) {
 74     return {
 75         get:function (receiver, name) {
 76             return obj[name] ? obj[name] : handler.call(obj, name);
 77         }
 78     }
 79 };
 80 
 81 /**
 82  * Creates a proxy for an object.
 83  * @param obj object to proxy
 84  * @param {Object} opts object with methods to define on the handler.
 85  * @memberOf comb
 86  */
 87 comb.handlerProxy = function (obj, opts) {
 88     opts = opts || {};
 89     return  Proxy.create(merge(handlerMaker(obj), opts));
 90 };
 91 
 92 /**
 93  * Creates a method missing proxy for an object.
 94  * <b>NOTE:</b> This method does not gurantee that the property will be used as a function call.
 95  *
 96  * @example
 97  *
 98  *  var x = {hello:function () {return "hello"}, world:"world"};
 99  *  var xHandler = comb.methodMissing(x, function (m) {
100  *              //you can do more interesting stuff in here!
101  *               return function () {
102  *                   return [m].concat(comb.argsToArray(arguments));
103  *               }
104  *   });
105  *  xHandler.hello(); //=> "hello"
106  *  xHandler.world //=> "world"
107  *  xHandler.someMethod("hello", "world"); //=> [ 'someMethod', 'hello', 'world' ]
108  *
109  * @param {Object} obj object to wrap with a method missing proxy
110  * @param {Function} handler handle to call when a property is missing
111  * @param {Object} opts prototype to assign to the proxy
112  * @memberOf comb
113  * @returns {Proxy} a proxy
114  */
115 comb.methodMissing = function (obj, handler, proto) {
116     proto = proto || {};
117     return  Proxy.create(merge(handlerMaker(obj), noSuchMethodHandler(obj, handler)), object.isHash(proto) ? proto : proto.prototype);
118 };
119 
120 /**
121  *  Determines if the object is a proxy or not.
122  *
123  * @param {Anything} obj object to test
124  * @memberOf comb
125  * @returns {Boolean} true if it is a proxy false otherwise
126  */
127 comb.isProxy = function (obj) {
128     var undef;
129     return obj !== undef && obj !== null && Proxy.isProxy(obj);
130 }
131 
132 /**
133  * Creates a function proxy for an object.
134  *
135  * @example
136  *
137  * //create an object that can use properties or as a function through the new operator
138  * var MyObject = comb.define(null, {
139  *     instance : {
140  *         hello : "hello",
141  *         constructor : function(){
142  *             this.args = comb.argsToArray(arguments);
143  *         }
144  *     }
145  * });
146  *
147  * //NOTE: this will not work properly for native objects like Date.
148  * var createNewMyObject = function(){
149  *    try {
150  *      p = new MyObject();
151  *     } catch (ignore) {
152  *          //ignore the error because its probably from missing arguments
153  *     }
154  *     //Now lets take care of arguments supplied!!!
155  *     return MyObject.apply(p, comb.argsToArray(arguments));
156  * };
157  *
158  * //This example creates an object with a world property but its not a function!
159  * var handle = comb.createFunctionWrapper({world : "world"}, createNewMyObject, createNewMyObject);
160  *
161  * handle.world //=> "world"
162  * var a = handle(1);
163  * a.hello;  //=>"hello"
164  * a.args; //=> [1];
165  * a = new handle(1,2);
166  * a.hello; //=>"hello"
167  * a.args; //=> [1,2];
168  * @param obj the object to proxy
169  * @param {Function} handler the handler to call when the object is used as a function
170  * @param {Function} constructTrap the funciton to use when using new on the object
171  * @param {Object} opts the prototype of the object.
172  * @memberOf comb
173  */
174 comb.createFunctionWrapper = function (obj, handler, constructTrap, opts) {
175     var args = misc.argsToArray(arguments), ret;
176     if (args.length != 4) {
177         opts = object.isHash(args[args.length - 1]) ? args.pop() : null;
178         constructTrap = functions.isFunction(args[args.length - 1]) ? args.pop() : null;
179         handler = functions.isFunction(args[args.length - 1]) ? args.pop() : null;
180     }
181     if (misc.isUndefined(obj)) throw new Error("obj required when using create function wrapper");
182     if (functions.isFunction(constructTrap) && !functions.isFunction(handler)) {
183         ret = Proxy.createFunction(handlerMaker(obj), constructTrap);
184     } else {
185         ret = Proxy.createFunction(handlerMaker(obj), handler, constructTrap);
186     }
187 
188 
189     if (opts) {
190         Proxy.setPrototype(ret, object.isHash(opts) ? opts : opts.prototype);
191     }
192     return ret;
193 };
194 
195 
196 comb.Proxy = Proxy;