]> err.no Git - sope/blob - Recycler/NGJavaScript/NGJavaScriptObject.m
removed NGCString
[sope] / Recycler / NGJavaScript / NGJavaScriptObject.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21 // $Id$
22
23 #include "NGJavaScriptObject.h"
24 #include "NGJavaScriptContext.h"
25 #include <NGExtensions/NGExtensions.h>
26 #include <NGScripting/NGObjectMappingContext.h>
27 #include "NGJavaScriptObjectMappingContext.h"
28 #include "common.h"
29 #include "globals.h"
30
31 //#define GREEDY_ARCHIVE 1
32
33 @interface JSIDEnum : NSEnumerator
34 {
35   NGJavaScriptObjectMappingContext *ctx;
36   JSContext *cx;
37   JSIdArray *idArray;
38   unsigned  pos;
39   id        object;
40 }
41 - (id)initWithIdArray:(JSIdArray *)_array 
42   mappingContext:(NGJavaScriptObjectMappingContext *)_ctx;
43 - (id)initWithIdArray:(JSIdArray *)_array 
44   object:(id)_object
45   mappingContext:(NGJavaScriptObjectMappingContext *)_ctx;
46 @end
47
48 @interface JSObjChainEnum : NSEnumerator
49 {
50   id  object;
51   SEL selector;
52 }
53 - (id)initWithObject:(id)_obj selector:(SEL)_sel;
54 @end
55
56 @implementation NGJavaScriptObject
57
58 - (id)initWithHandle:(void *)_handle
59   inMappingContext:(NGObjectMappingContext *)_ctx
60 {
61   NSAssert(_handle, @"Missing handle ..");
62   NSAssert(_ctx,    @"Missing context ..");
63
64   self->handle = _handle;
65   self->ctx    = [_ctx retain];
66   
67   self->jscx =
68     [[(NGJavaScriptObjectMappingContext *)self->ctx jsContext] handle];
69   
70   self->addedRoot =
71     JS_AddNamedRoot(self->jscx, &(self->handle), self->isa->name);
72   NSAssert(self->addedRoot, @"couldn't add root !");
73   
74   return self;
75 }
76
77 + (void *)jsObjectClass {
78   return NULL;
79 }
80
81 - (void *)createJSObjectForJSClass:(void *)_class inJSContext:(void *)jsctx {
82   return JS_NewObject(jsctx, _class, NULL, NULL);
83 }
84
85 - (id)initWithJSClass:(void *)_class {
86   NGJavaScriptObjectMappingContext *mctx;
87   
88   if ((mctx = [NGJavaScriptObjectMappingContext activeObjectMappingContext])) {
89     void *jsctx;
90     void *jso;
91     
92     jsctx = [[mctx jsContext] handle];
93     
94     if ((jso = [self createJSObjectForJSClass:_class inJSContext:jsctx])) {
95       self = [self initWithHandle:jso inMappingContext:mctx];
96       [mctx registerObject:self forImportedHandle:jso];
97       return self;
98     }
99     else {
100       [self release]; self = nil;
101       
102       [NSException raise:@"NGJavaScriptException"
103                    format:@"couldn't create JS object .."];
104       return nil;
105     }
106   }
107   [self release];
108   [NSException raise:@"NGJavaScriptException"
109                format:@"missing active mapping context !"];
110   return nil;
111 }
112 - (id)init {
113   return [self initWithJSClass:[[self class] jsObjectClass]];
114 }
115
116 - (void)dealloc {
117   if (NGJavaScriptBridge_TRACK_MEMORY) {
118     NSLog(@"%s: dealloc o0x%08X j0x%08X ctx=0x%08X jcx=0x%08X",
119           __PRETTY_FUNCTION__, self, self->handle,
120           self->ctx, self->jscx);
121   }
122   
123   if (self->handle) {
124     if (self->addedRoot)
125       JS_RemoveRoot(self->jscx, &self->handle);
126     [self->ctx forgetImportedHandle:self->handle];
127   }
128   else {
129     if (self->addedRoot)
130       NSLog(@"%s: missing handle !, couldn't remove root ..", __PRETTY_FUNCTION__);
131   }
132   
133   [self->ctx release];
134   [super dealloc];
135 }
136
137 /* transformation */
138
139 - (BOOL)_jsGetValue:(void *)_value inJSContext:(NGJavaScriptContext *)_ctx {
140   *((jsval *)_value) = OBJECT_TO_JSVAL(self->handle);
141   return YES;
142 }
143 - (void *)_jsHandleInMapContext:(NGObjectMappingContext *)_ctx {
144   return self->handle;
145 }
146
147 /* misc */
148
149 - (void)applyStandardClasses {
150   if (!JS_InitStandardClasses(self->jscx, self->handle)) {
151     NSLog(@"couldn't load standard classes into JS object %@", self);
152     return;
153   }
154 }
155
156 - (void)setParentObject:(id)_parent {
157   JSObject *p;
158   
159   p = [self->ctx handleForObject:_parent];
160   
161   if (JS_SetParent(self->jscx, self->handle, p) == JS_FALSE) {
162     NSLog(@"couldn't set parent of object %@", self);
163   }
164 }
165 - (id)parentObject {
166   JSObject *p;
167   
168   if ((p = JS_GetParent(self->jscx, self->handle)) == NULL)
169     return nil;
170   
171   return [self->ctx objectForHandle:p];
172 }
173
174 - (NSEnumerator *)parentObjectChain {
175   NSEnumerator *e;
176   
177   e = [[JSObjChainEnum alloc] 
178         initWithObject:self selector:@selector(parentObject)];
179   return [e autorelease];
180 }
181
182 - (void)setPrototypeObject:(id)_proto {
183   JSObject *p;
184   
185   p = [self->ctx handleForObject:_proto];
186   
187   if (JS_SetPrototype(self->jscx, self->handle, p) == JS_FALSE) {
188     NSLog(@"couldn't set prototype of object %@", self);
189   }
190 }
191 - (id)prototypeObject {
192   JSObject *p;
193   
194   if ((p = JS_GetPrototype(self->jscx, self->handle)) == NULL)
195     return nil;
196   
197   return [self->ctx objectForHandle:p];
198 }
199
200 - (NSEnumerator *)prototypeObjectChain {
201   NSEnumerator *e;
202   
203   e = [[JSObjChainEnum alloc] 
204         initWithObject:self selector:@selector(prototypeObject)];
205   return AUTORELEASE(e);
206 }
207
208 - (BOOL)hasPropertyNamed:(NSString *)_key {
209   JSBool ret;
210   jsval  val = JSVAL_VOID;
211   unsigned int  clen;
212   unsigned char *ckey;
213   
214   clen = [_key cStringLength];
215   ckey = malloc(clen + 1);
216   [_key getCString:ckey];
217   
218   ret = JS_LookupProperty(self->jscx, self->handle, ckey, &val);
219   if (ret == JS_FALSE) {
220     NSLog(@"%s: WARNING: couldn't lookup property '%@'",
221           __PRETTY_FUNCTION__, _key);
222     free(ckey);
223     return NO;
224   }
225   if (val == JSVAL_VOID)
226     return NO;
227
228   return YES;
229 }
230
231 - (BOOL)hasFunctionNamed:(NSString *)_key {
232   JSBool        ret;
233   jsval         val = JSVAL_VOID;
234   unsigned int  clen;
235   unsigned char *ckey;
236   JSType        jsType;
237   
238   clen = [_key cStringLength];
239   ckey = malloc(clen + 1);
240   [_key getCString:ckey];
241   
242   ret = JS_GetProperty(self->jscx, self->handle, ckey, &val);
243   if (ret == JS_FALSE) {
244     NSLog(@"WARNING: couldn't lookup property '%@'", _key);
245     free(ckey);
246     return NO;
247   }
248   if (val == JSVAL_VOID)
249     return NO;
250   
251   jsType = JS_TypeOfValue(self->jscx, val);
252
253   return jsType == JSTYPE_FUNCTION ? YES : NO;
254 }
255
256 /* functions */
257
258 - (BOOL)isJavaScriptFunction {
259   jsval  val;
260   JSType jsType;
261   
262   val = OBJECT_TO_JSVAL(self->handle);
263   if (val == JSVAL_VOID)
264     return NO;
265   
266   jsType = JS_TypeOfValue(self->jscx, val);
267
268   return jsType == JSTYPE_FUNCTION ? YES : NO;
269 }
270 - (BOOL)isScriptFunction {
271   return [self isJavaScriptFunction];
272 }
273
274 - (id)_callOn:(id)_this argc:(int)_argc argv:(jsval *)_argv {
275   jsval    val;
276   JSBool   ret;
277   jsval    result;
278   JSObject *jso;
279   
280   val = OBJECT_TO_JSVAL(self->handle);
281   jso = [self->ctx handleForObject:_this];
282   
283   ret = JS_CallFunctionValue(self->jscx, jso, val,
284                              _argc, _argv,
285                              &result);
286   if (ret == JS_TRUE)
287     return [self->ctx objectForJSValue:&result];
288   
289   NSLog(@"%s: couldn't run function %@", __PRETTY_FUNCTION__, self);
290   return nil;
291 }
292 - (id)callOn:(id)_this {
293   return [self _callOn:_this argc:0 argv:NULL];
294 }
295 - (id)callOn:(id)_this withObject:(id)_arg0 {
296   jsval arg0;
297   
298   if ([self->ctx jsValue:&arg0 forObject:_arg0])
299     return [self _callOn:_this argc:1 argv:&arg0];
300   
301   NSLog(@"%s: couldn't convert arg0 %@ for function %@", __PRETTY_FUNCTION__,
302         _arg0, self);
303   return nil;
304 }
305
306 /* mimic dictionary */
307
308 - (NSArray *)allKeys {
309   NSEnumerator   *e;
310   NSString       *key;
311   NSMutableArray *keys;
312   
313   if ((e = [self keyEnumerator]) == nil) return nil;
314   keys = [NSMutableArray arrayWithCapacity:8];
315   while ((key = [e nextObject]))
316     [keys addObject:key];
317   return keys;
318 }
319 - (NSArray *)allValues {
320   NSEnumerator   *e;
321   id object;
322   NSMutableArray *keys;
323   
324   if ((e = [self objectEnumerator]) == nil) return nil;
325   keys = [NSMutableArray arrayWithCapacity:8];
326   while ((object = [e nextObject]))
327     [keys addObject:object];
328   return keys;
329 }
330
331 - (NSEnumerator *)keyEnumerator {
332   JSIDEnum *e;
333   JSIdArray *idArray;
334   
335   if ((idArray = JS_Enumerate(self->jscx, self->handle)) == NULL) {
336     NSLog(@"couldn't enumerate object ..");
337     return nil;
338   }
339   
340   e = [[JSIDEnum alloc] initWithIdArray:idArray mappingContext:self->ctx];
341   return AUTORELEASE(e);
342 }
343 - (NSEnumerator *)objectEnumerator {
344   JSIDEnum *e;
345   JSIdArray *idArray;
346   
347   if ((idArray = JS_Enumerate(self->jscx, self->handle)) == NULL) {
348     NSLog(@"couldn't enumerate object ..");
349     return nil;
350   }
351   
352   e = [[JSIDEnum alloc] initWithIdArray:idArray 
353                         object:self
354                         mappingContext:self->ctx];
355   return AUTORELEASE(e);
356 }
357
358 - (void)setObject:(id)_obj forStringKey:(NSString *)_key {
359   jsval         v, lv;
360   JSBool        res;
361   unsigned int  clen;
362   unsigned char *ckey;
363   
364   if (![self->ctx jsValue:&v forObject:_obj]) {
365     NSLog(@"WARNING: couldn't convert ObjC value to JS: %@", _obj);
366     return;
367   }
368   
369   clen = [_key cStringLength];
370   ckey = malloc(clen + 1);
371   [_key getCString:ckey];
372   
373   res = JS_LookupProperty(self->jscx, self->handle, ckey, &lv);
374   if (res == JS_FALSE) {
375     NSLog(@"WARNING: couldn't lookup property '%@'", _key);
376     free(ckey);
377     return;
378   }
379   
380   if (lv == JSVAL_VOID) {
381     /* property does not exist */
382     res = JS_DefineProperty(self->jscx, self->handle, ckey, v,
383                             NULL /* getter */,
384                             NULL /* setter */,
385                             JSPROP_ENUMERATE|JSPROP_EXPORTED);
386   }
387   else {
388     /* property does exist */
389     res = JS_SetProperty(self->jscx, self->handle, ckey, &v);
390   }
391   
392   free(ckey); ckey = NULL;
393   
394   if (res == JS_FALSE) {
395     NSLog(@"WARNING: couldn't set ObjC value %@ to JS %@", _obj, _key);
396     return;
397   }
398 }
399
400 - (id)objectForStringKey:(NSString *)_key {
401   JSBool ret;
402   jsval  val = JSVAL_VOID;
403   unsigned int  clen;
404   unsigned char *ckey;
405   
406   clen = [_key cStringLength];
407   ckey = malloc(clen + 1);
408   [_key getCString:ckey];
409   
410   ret = JS_GetProperty(self->jscx, self->handle, ckey, &val);
411   if (ret == JS_FALSE) {
412     NSLog(@"WARNING(%s): couldn't get value of property %@ ",
413           __PRETTY_FUNCTION__, _key);
414     free(ckey);
415     return nil;
416   }
417   
418   free(ckey); ckey = NULL;
419   
420   if (val == JSVAL_VOID) {
421     /* property is not defined */
422 #if 0
423     NSLog(@"%s: got void for key '%s' o0x%08X j0x%08X",
424           __PRETTY_FUNCTION__,
425           ckey, self, self->handle);
426 #endif
427     return nil;
428   }
429   
430   return [self->ctx objectForJSValue:&val];
431 }
432
433 - (void)removeObjectForStringKey:(NSString *)_key {
434   JSBool ret;
435   
436   ret = JS_DeleteProperty(self->jscx, self->handle, [_key cString]);
437   if (ret == JS_FALSE) {
438     NSLog(@"WARNING: couldn't delete property %@ ", _key);
439     return;
440   }
441 }
442
443 - (BOOL)isStringKey:(id)_key {
444   return [_key isKindOfClass:[NSString class]];
445 }
446 - (id)unableToHandleKey:(id)_key {
447   NSLog(@"Unable to handle key: %@\n  key class: %@\n  object: %@\n  object class: %@",
448         _key, [_key class], self, [self class]);
449   return nil;
450 }
451
452 - (id)objectForKey:(id)_key {
453   if ([self isStringKey:_key])
454     return [self objectForStringKey:_key];
455   else
456     return [self unableToHandleKey:_key];
457 }
458 - (void)setObject:(id)_obj forKey:(id)_key {
459   if ([self isStringKey:_key]) {
460     [self setObject:_obj forStringKey:(id)_key];
461   }
462   else
463     [self unableToHandleKey:_key];
464 }
465 - (void)removeObjectForKey:(id)_key {
466   if ([self isStringKey:_key])
467     [self removeObjectForStringKey:_key];
468   else
469     [self unableToHandleKey:_key];
470 }
471
472 /* convert to dictionary */
473
474 - (NSDictionary *)convertToNSDictionary {
475   /* could be made far more efficient ... */
476   NSEnumerator   *e;
477   NSString       *key;
478   NSMutableDictionary *dict;
479   
480   if ((e = [self keyEnumerator]) == nil) return nil;
481
482   dict = [NSMutableDictionary dictionaryWithCapacity:16];
483   while ((key = [e nextObject])) {
484     id value = [self objectForKey:key];
485     
486     [dict setObject:value?value:[NSNull null] forKey:key];
487   }
488   return dict;
489 }
490
491 /* KVC */
492
493 - (void)takeValue:(id)_value forKey:(NSString *)_key {
494 #if 0
495   if (_value == nil)
496     ;
497 #endif
498   [self setObject:_value forKey:_key];
499 }
500 - (id)valueForKey:(NSString *)_key {
501   return [self objectForKey:_key];
502 }
503
504 /* private */
505
506 - (void *)handle {
507   return self->handle;
508 }
509
510 - (void)makeGlobal {
511   JS_SetGlobalObject(self->jscx, self->handle);
512 }
513
514 - (NSString *)javaScriptClassName {
515   if (self->handle == nil)
516     return nil;
517   return [NSString stringWithCString:JS_GetClass(self->handle)->name];
518 }
519
520 /* NSCoding */
521
522 - (void)decodeJavaScriptPropertiesWithCoder:(NSCoder *)_coder {
523   NSDictionary *props;
524   NSEnumerator *keys;
525   NSString     *key;
526
527   props = [_coder decodeObject];
528     
529   keys = [props keyEnumerator];
530   while ((key = [keys nextObject])) {
531     id value = [props objectForKey:key];
532       
533     if ([value isNotNull])
534       [self setObject:value forKey:key];
535     else
536       [self setObject:nil forKey:key];
537   }
538 }
539 - (void)encodeJavaScriptPropertiesWithCoder:(NSCoder *)_coder {
540   NSMutableDictionary *props;
541   NSEnumerator        *keys;
542   NSString            *key;
543   
544   props = [NSMutableDictionary dictionaryWithCapacity:16];
545   keys = [self keyEnumerator];
546   while ((key = [keys nextObject])) {
547     id value;
548     
549     if ((value = [self objectForKey:key])) {
550       if ([value isJavaScriptFunction]) {
551         [self debugWithFormat:@"did not encode JS function object: %@", value];
552         continue;
553       }
554       
555       [props setObject:value forKey:key];
556     }
557     else
558       [props setObject:[NSNull null] forKey:key];
559   }
560   [_coder encodeObject:props];
561 }
562
563 - (id)initWithCoder:(NSCoder *)_coder {
564   if ((self = [self init])) {
565     NSString *jsClass;
566     id lParent, lPrototype;
567   
568     jsClass    = [_coder decodeObject];
569     lParent    = [_coder decodeObject];
570     lPrototype = [_coder decodeObject];
571   
572     [self setParentObject:lParent];
573     [self setPrototypeObject:lPrototype];
574
575     [self decodeJavaScriptPropertiesWithCoder:_coder];
576     
577     if (![[self javaScriptClassName] isEqualToString:jsClass]) {
578       [self logWithFormat:@"WARNING: decoded object is not JS class %@ !!", jsClass];
579     }
580   }
581   return self;
582 }
583 - (void)encodeWithCoder:(NSCoder *)_coder {
584   [_coder encodeObject:[self javaScriptClassName]];
585 #if GREEDY_ARCHIVE
586   [_coder encodeObject:[self parentObject]];
587   [_coder encodeObject:[self prototypeObject]];
588 #else
589   [_coder encodeConditionalObject:[self parentObject]];
590   [_coder encodeConditionalObject:[self prototypeObject]];
591 #endif
592   [self encodeJavaScriptPropertiesWithCoder:_coder];
593 }
594
595 /* description */
596
597 - (NSString *)description {
598   NSMutableString *ms;
599   id tmp;
600   
601   ms = [NSMutableString stringWithCapacity:32];
602   [ms appendFormat:@"<%@[0x%08X]: handle=0x%08X>",
603                      NSStringFromClass([self class]), self,
604                      [self handle]];
605   if ((tmp = [self javaScriptClassName]))
606     [ms appendFormat:@" class=%@", tmp];
607   
608   if ([self isJavaScriptFunction])
609     [ms appendString:@" function"];
610   
611   [ms appendString:@">"];
612   return ms;
613 }
614
615 @end /* NGJavaScriptObject */
616
617 @implementation JSIDEnum
618
619 - (id)initWithIdArray:(JSIdArray *)_array 
620   mappingContext:(NGJavaScriptObjectMappingContext *)_ctx
621 {
622   if (_array == NULL) {
623     RELEASE(self);
624     return nil;
625   }
626   self->idArray = _array;
627   self->ctx = _ctx;
628   self->cx  = [[_ctx jsContext] handle];
629   return self;
630 }
631 - (id)initWithIdArray:(JSIdArray *)_array 
632   object:(id)_object
633   mappingContext:(NGJavaScriptObjectMappingContext *)_ctx
634 {
635   if ((self = [self initWithIdArray:_array mappingContext:_ctx])) {
636     self->object = RETAIN(_object);
637   }
638   return self;
639 }
640
641 - (void)dealloc {
642   if (self->idArray)
643     JS_DestroyIdArray(self->cx, self->idArray);
644   RELEASE(self->object);
645   [super dealloc];
646 }
647
648 - (id)nextObject {
649   jsid  jid;
650   jsval idv;
651   id    jobj = nil;
652   
653   if (self->idArray == NULL)
654     return nil;
655   
656   if (self->idArray->length <= self->pos) {
657     JS_DestroyIdArray(self->cx, self->idArray);
658     self->idArray = NULL;
659     return nil;
660   }
661   
662   jid = self->idArray->vector[self->pos];
663   
664   if (JS_IdToValue(self->cx, jid, &idv) == JS_FALSE) {
665     NSLog(@"couldn't convert id to value ..");
666     return nil;
667   }
668   
669   jobj = [self->ctx objectForJSValue:&idv];
670   
671   if (self->object)
672     jobj = [(NSDictionary *)self->object objectForKey:jobj];
673   
674   self->pos++;
675   
676   if (self->idArray->length <= self->pos) {
677     JS_DestroyIdArray(self->cx, self->idArray);
678     self->idArray = NULL;
679   }
680   
681   return jobj;
682 }
683
684 - (NSString *)description {
685   return [NSString stringWithFormat:
686                      @"<0x%08X[%@]: len=%d>",
687                      self, NSStringFromClass([self class]),
688                      self->idArray ? self->idArray->length : 0];
689 }
690
691 @end /* JSIDEnum */
692
693 @implementation JSObjChainEnum
694
695 - (id)initWithObject:(id)_obj selector:(SEL)_sel {
696   self->object   = RETAIN(_obj);
697   self->selector = _sel;
698   return self;
699 }
700 - (void)dealloc {
701   RELEASE(self->object);
702   [super dealloc];
703 }
704
705 - (id)nextObject {
706   AUTORELEASE(self->object);
707   self->object = RETAIN([self->object performSelector:self->selector]);
708   return self->object;
709 }
710
711 @end /* JSObjChainEnum */