]> err.no Git - sope/blob - Recycler/NGJavaScript/NGJavaScriptObjectMappingContext.m
removed NGCString
[sope] / Recycler / NGJavaScript / NGJavaScriptObjectMappingContext.m
1 /*
2   Copyright (C) 2000-2003 SKYRIX Software AG
3
4   This file is part of OGo
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 "NGJavaScriptObjectMappingContext.h"
24 #include "NGJavaScriptContext.h"
25 #include "NGJavaScriptObjectHandler.h"
26 #include "NGJavaScriptObject.h"
27 #include "NGJavaScriptShadow.h"
28 #include "NGJavaScriptFunction.h"
29 #include "NSString+JS.h"
30 #include "common.h"
31 #include "globals.h"
32
33 #if GNUSTEP_BASE_LIBRARY
34 #  include <GNUstepBase/behavior.h>
35 #endif
36
37 @interface NSObject(CombinedObjects)
38 - (void)jsObjectFinalized:(void *)_handle;
39 - (BOOL)_jsGetValue:(void *)_value inJSContext:(NGJavaScriptContext *)_ctx;
40 - (id)_js_parentObject;
41 @end
42
43 @interface NGJavaScriptObjectMappingContext(Privates)
44 - (void)_jsFinalizeCombinedObject:(id)_object;
45 @end
46
47 typedef struct {
48   JSObject                         *jso;
49   NGJavaScriptObjectMappingContext *ctx;
50   NGJavaScriptObjectHandler        *handler;
51   BOOL                             rootRef;
52   unsigned short                   rc;
53 } JSCombinedObjInfo;
54
55 extern JSClass ObjCShadow_JSClass;
56
57 @implementation NGJavaScriptObjectMappingContext
58
59 static BOOL       logHandleForObject = NO;
60 static BOOL       logValueConversion = NO;
61 static NSMapTable *combinedToInfo = NULL; // combined objects
62
63 + (void)initialize {
64   static BOOL didInit = NO;
65   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
66   if (didInit) return;
67   
68   NGJavaScriptBridge_LOG_PROP_DEFINITION
69     = [[ud objectForKey:@"jsLogPropDef"] boolValue];
70   NGJavaScriptBridge_LOG_FUNC_DEFINITION
71     = [[ud objectForKey:@"jsLogFuncDef"] boolValue];
72
73   logHandleForObject = [ud boolForKey:@"JSLogHandleForObject"];
74   logValueConversion = [ud boolForKey:@"JSLogValueConversion"];
75   
76   didInit = YES;
77 }
78
79 - (id)initWithJSContext:(NGJavaScriptContext *)_ctx {
80   if ((self = [super init])) {
81     self->jsContext = [_ctx retain];
82     
83     /* 'combined' ObjC-JS objects */
84     if (combinedToInfo == NULL) {
85       combinedToInfo =
86         NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
87                          NSOwnedPointerMapValueCallBacks,
88                          200);
89     }
90     
91     /* 'pure' ObjC objects */
92     self->objcToJS =
93       NSCreateMapTable(NSObjectMapKeyCallBacks,
94                        NSNonOwnedPointerMapValueCallBacks,
95                        200);
96     
97     /* 'pure' JS objects */
98     self->jsToObjC =
99       NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
100                        NSNonRetainedObjectMapValueCallBacks,
101                        200);
102     
103     /* make default global */
104     
105     [self pushContext];
106     {
107       NGJavaScriptObject *glob;
108
109       glob = [[NGJavaScriptObject alloc] init];
110       [glob applyStandardClasses];
111       [self setGlobalObject:glob];
112       [glob release];
113     }
114     [self popContext];
115   }
116   return self;
117 }
118 - (id)init {
119   NGJavaScriptContext *ctx;
120   
121   ctx = [[NGJavaScriptContext alloc] init];
122   self = [self initWithJSContext:ctx];
123   [ctx release]; ctx = nil;
124   return self;
125 }
126
127 - (void)dealloc {
128   if (self->jsToObjC) NSFreeMapTable(self->jsToObjC);
129   if (self->objcToJS) NSFreeMapTable(self->objcToJS);
130   [self->jsContext release];
131   [super dealloc];
132 }
133
134 - (NGJavaScriptContext *)jsContext {
135   return self->jsContext;
136 }
137
138 /* hierachy */
139
140 - (void)setGlobalObject:(id)_object {
141   JSObject *glob;
142   
143   glob = [self handleForObject:_object];
144   JS_SetGlobalObject([self->jsContext handle], glob);
145 }
146 - (id)globalObject {
147   JSObject *glob;
148   
149   if ((glob = JS_GetGlobalObject([self->jsContext handle])) == NULL)
150     return nil;
151   
152   return [self objectForHandle:glob];
153 }
154
155 /* proxy factory */
156
157 - (void *)proxyForObject:(id)_object {
158   /* this is called by handleForObject: */
159   NGJavaScriptObjectHandler *jsHandler;
160   void *jso;
161   
162   jsHandler =
163     [[NGJavaScriptObjectHandler alloc] initWithObject:_object
164                                        inMappingContext:self];
165   
166   jso = [jsHandler handle];
167   
168   [jsHandler autorelease];
169   
170   return jso;
171 }
172 - (id)proxyForPureJSObject:(void *)_handle {
173   Class proxyClass;
174   id    proxy;
175   JSContext *cx;
176   void      *jsClazz;
177   
178   if (_handle == NULL)
179     return nil;
180   
181   /* create a proxy for a 'pure' JavaScript object */
182   
183   cx = [self->jsContext handle];
184   jsClazz = OBJ_GET_CLASS(cx, (JSObject *)_handle);
185   
186   /* use a configurable mapping of JS-class to ObjC class here ? */
187   if (jsClazz == &js_ArrayClass)
188     proxyClass = [NGJavaScriptArray class];
189   else if (jsClazz == &js_FunctionClass)
190     proxyClass = [NGJavaScriptFunction class];
191   else
192     proxyClass = [NGJavaScriptObject class];
193   
194   proxy = [[proxyClass alloc] initWithHandle:_handle inMappingContext:self];
195   return AUTORELEASE(proxy);
196 }
197
198 /* mappings */
199
200 - (void *)handleForObject:(id)_object {
201   /*
202     What is the _object in this context ?
203     - I guess it's a NGJavaScriptObject
204       - often, but not always, see testjs: called with Blah and MyNum
205       - only "custom" objects, not NGJavaScriptObject are registered !
206     
207     - it checks whether the object itself can return handle 
208       (_jsHandleInMapContext:)
209       - this seems always true for NGJavaScriptObject's !
210     - checks, whether a proxy is already registered
211       - I guess a proxy is a JS-object with an attached JSObjectHandler ?
212     - otherwise create a new one
213     - register new one
214     - check parent object of new one
215   */
216   JSCombinedObjInfo *combinedObjInfo;
217   void *jso;
218   id   parent;
219   
220   if (logHandleForObject)
221     NSLog(@"-proxyForObject:0x%08X %@", _object, _object);
222   
223   if (_object == nil) {
224     jso = JSVAL_TO_OBJECT(JSVAL_NULL);
225     if (logHandleForObject) NSLog(@"  => is nil: 0x%08X", jso);
226     return jso;
227   }
228   
229   if ([_object respondsToSelector:@selector(_jsHandleInMapContext:)]) {
230     jso = [_object _jsHandleInMapContext:self];
231     if (logHandleForObject) {
232       NSLog(@"  obj (class %@) handles itself: 0x%08X", 
233             NSStringFromClass([_object class]), jso);
234     }
235     return jso;
236   }
237   
238   if ((jso = NSMapGet(self->objcToJS, _object))) {
239     /* a proxy is already registered */
240     if (logHandleForObject) NSLog(@"  proxy already registered: 0x%08X", jso);
241     return jso;
242   }
243   
244   if ((combinedObjInfo = NSMapGet(combinedToInfo, _object))) {
245     /* check for correct context */
246     if (combinedObjInfo->ctx != self) {
247       NSLog(@"%s: tried to access combined object 0x%08X<%@> in "
248             @"different mapping context (ctx=0x%08X, required=0x%08X)  !",
249             __PRETTY_FUNCTION__, _object, NSStringFromClass([_object class]),
250             self, combinedObjInfo->ctx);
251       return nil;
252     }
253     if (logHandleForObject) NSLog(@"  proxy is combined object: 0x%08X", jso);
254     return combinedObjInfo->jso;
255   }
256   
257   /* create a new proxy */
258   
259   if (logHandleForObject) NSLog(@"  creating proxy ...");
260   if ((jso = [self proxyForObject:_object])) {
261     /* register proxy */
262 #if DEBUG
263     NSAssert1(NSMapGet(self->objcToJS, _object) == NULL,
264               @"already registered a proxy for object o0x%08X", _object);
265 #endif
266     if (logHandleForObject) NSLog(@"  register handle 0x%08X ...", jso);
267     NSMapInsertKnownAbsent(self->objcToJS, _object, jso);
268   }
269   else {
270     NSLog(@"ERROR(%s): proxy creation failed: %@", 
271           __PRETTY_FUNCTION__, _object);
272     return NULL;
273   }
274   
275   /* look for parent of new proxy */
276   
277   if ((parent = [_object _js_parentObject])) {
278     void *pjso;
279     JSBool res;
280     
281     if (logHandleForObject) 
282       NSLog(@"register parent 0x%08X for object .."), parent;
283     
284     pjso = [self handleForObject:parent];
285     
286     res = JS_SetParent([self->jsContext handle], jso, pjso);
287     
288     if (res == JS_FALSE) {
289       NSLog(@"WARNING: ctx %@ couldn't register JS parent %@ on object %@",
290             self, parent, _object);
291     }
292   }
293   
294   return jso;
295 }
296
297 - (void)registerObject:(id)_object forImportedHandle:(void *)_handle {
298 #if DEBUG
299   NSAssert(_object, @"missing object");
300   NSAssert(_handle, @"missing handle");
301 #endif
302   NSMapInsertKnownAbsent(self->jsToObjC, _handle, _object);
303 }
304
305 - (id)objectForHandle:(void *)_handle {
306   /*
307     What does it do ? What is the return value ? TODO: Document !
308   */
309   extern JSClass NGJavaScriptObjectHandler_JSClass;
310   JSClass *handleClass;
311   id obj;
312   
313   if (_handle == NULL)
314     return nil;
315   
316   if ((handleClass = JS_GetClass(_handle)) == NULL) {
317     NSLog(@"couldn't get class of handle 0x%08X", (unsigned)_handle);
318     return nil;
319   }
320   
321   /* check for 'reflected' JavaScript objects (combined or ObjC exported) */
322   
323   if (handleClass == &NGJavaScriptObjectHandler_JSClass) {
324     NGJavaScriptObjectHandler *h;
325     
326     if ((h = JS_GetPrivate([self->jsContext handle], _handle)) == nil) {
327       NSLog(@"couldn't get private of JS object 0x%08X "
328             @"(NGJavaScriptObjectHandler)", _handle);
329       return nil;
330     }
331     
332     return AUTORELEASE(RETAIN([h managedObject]));
333   }
334   
335   if (handleClass == &ObjCShadow_JSClass) {
336     NGJavaScriptShadow *h;
337     
338     if ((h = JS_GetPrivate([self->jsContext handle], _handle)) == nil) {
339       NSLog(@"couldn't get private of JS shadow object 0x%08X "
340             @"(NGJavaScriptShadow)", _handle);
341       return nil;
342     }
343     
344     return AUTORELEASE(RETAIN([h masterObject]));
345   }
346   
347   /* check for 'pure' JavaScript objects */
348   
349   if ((obj = NSMapGet(self->jsToObjC, _handle)))
350     /* found */
351     return AUTORELEASE(RETAIN(obj));
352   
353   if ((obj = [self proxyForPureJSObject:_handle])) {
354     /* register proxy */
355     [self registerObject:obj forImportedHandle:_handle];
356     return obj;
357   }
358   
359   /* couldn't build a proxy */
360   return nil;
361 }
362
363 - (void)forgetObject:(id)_object {
364   JSCombinedObjInfo *combinedObjInfo;
365   
366   NSAssert(_object, @"missing object ..");
367   
368   if ((combinedObjInfo = NSMapGet(combinedToInfo, _object))) {
369     if (combinedObjInfo->ctx != self) {
370       NSLog(@"forget combined object 0x%08X in wrong context !", _object);
371     }
372     else
373       [self _jsFinalizeCombinedObject:_object];
374   }
375   else {
376     if (NGJavaScriptBridge_TRACK_FORGET) {
377       JSObject *jso;
378       
379       jso = NSMapGet(self->objcToJS, _object);
380       NSLog(@"forgetting non-combined object o0x%08X<%@> j0x%08X rc %d",
381             _object, NSStringFromClass([_object class]),
382             jso,
383             [_object retainCount]);
384     }
385     
386     NSMapRemove(self->objcToJS, _object);
387
388 #if 0
389     [self->jsContext performSelector:@selector(collectGarbage)
390                      withObject:nil
391                      afterDelay:0.0];
392 #endif
393   }
394 }
395 - (void)forgetImportedHandle:(void *)_handle {
396   NSMapRemove(self->jsToObjC, _handle);
397 }
398
399 /* handler */
400
401 - (NGJavaScriptObjectHandler *)handlerForObject:(id)_object {
402   JSObject *jso;
403   
404   if ((jso = [self handleForObject:_object]) == NULL) {
405     NSLog(@"did not find handle for object 0x%08X", _object);
406     return nil;
407   }
408   
409   return JS_GetPrivate([self->jsContext handle], jso);
410 }
411
412 /* garbage collection */
413
414 - (id)popContext {
415   [self->jsContext collectGarbage];
416   return [super popContext];
417 }
418
419 - (void)collectGarbage {
420   [self pushContext];
421   [self->jsContext collectGarbage];
422   [self popContext];
423 }
424
425 /* logging */
426
427 - (void)_logExportedJavaScriptObjects {
428   NSMapEnumerator e;
429   JSObject *jso;
430   JSClass  *jsClass;
431   id       proxy;
432
433   if (NSCountMapTable(jsToObjC) < 1) {
434     printf("no imported JavaScript objects.\n");
435     return;
436   }
437   
438   e = NSEnumerateMapTable(jsToObjC);
439   printf("imported JavaScript objects:\n");
440   printf("  %-10s %-20s %-10s %-26s %-2s\n",
441          "JS",
442          "JS-Class",
443          "ObjC",
444          "ObjC-Class",
445          "rc");
446   while (NSNextMapEnumeratorPair(&e, (void*)&jso, (void*)&proxy)) {
447     jsClass = jso ? JS_GetClass(jso) : NULL;
448     
449     printf("  0x%08X %-20s 0x%08X %-26s %2d\n",
450            (unsigned)jso,
451            jsClass ? jsClass->name : "<null>",
452            (unsigned)proxy,
453            [NSStringFromClass([proxy class]) cString],
454            [proxy retainCount]);
455   }
456 }
457
458 - (void)_logExportedObjCObjects {
459   NSMapEnumerator e;
460   id       object;
461   JSObject *jsProxy;
462
463   if (NSCountMapTable(objcToJS) < 1) {
464     printf("no exported Objective-C objects.\n");
465     return;
466   }
467   
468   printf("exported Objective-C objects:\n");
469   printf("  %-10s %-20s %-10s %-26s %-2s\n",
470          "ObjC",
471          "ObjC-Class",
472          "JS",
473          "JS-Class",
474          "rc");
475   e = NSEnumerateMapTable(objcToJS);
476   while (NSNextMapEnumeratorPair(&e, (void*)&object, (void*)&jsProxy)) {
477     JSClass *jsClass;
478     
479     jsClass = jsProxy ? JS_GetClass(jsProxy) : NULL;
480     
481     printf("  0x%08X %-20s 0x%08X %-26s %2d\n",
482            (unsigned)object,
483            [NSStringFromClass([object class]) cString],
484            (unsigned)jsProxy,
485            jsClass ? jsClass->name : "<null>",
486            [object retainCount]);
487   }  
488 }
489
490 /* values */
491
492 - (id)objectForJSValue:(void *)_value {
493   JSType    jsType;
494   JSBool    couldConvert;
495   JSContext *cx;
496   
497   couldConvert = JS_FALSE;
498   
499   if (JSVAL_IS_NULL(*(jsval *)_value))
500     return nil;
501   
502   cx = [self->jsContext handle];
503   jsType = JS_TypeOfValue(cx, *(jsval *)_value);
504   switch (jsType) {
505     case JSTYPE_VOID:
506       return nil;
507       
508     case JSTYPE_FUNCTION:
509     case JSTYPE_OBJECT: {
510       JSObject *obj;
511       
512       if (!(couldConvert = JS_ValueToObject(cx, *(jsval *)_value, &obj)))
513         break;
514       
515       return [self objectForHandle:obj];
516     }
517     
518 #if 0
519     case JSTYPE_FUNCTION: {
520       JSFunction *func;
521       
522       if ((func = JS_ValueToFunction(cx, *(jsval *)_value))) {
523         static Class FuncClass = Nil;
524         
525         if (FuncClass == Nil)
526           FuncClass = NSClassFromString(@"NGJavaScriptFunction");
527
528         NSAssert(FuncClass, @"missing JS function class ..");
529         
530         return AUTORELEASE([[FuncClass alloc] initWithHandle:func
531                                               mappingContext:self]);
532       }
533       else {
534         NSLog(@"%s: couldn't get JS function ..", __PRETTY_FUNCTION__);
535         couldConvert = NO;
536       }
537       break;
538     }
539 #endif
540       
541     case JSTYPE_STRING: {
542       JSString *s;
543
544       if ((s = JS_ValueToString(cx, *(jsval *)_value))) {
545         return [NSString stringWithJavaScriptString:s];
546       }
547       else
548         couldConvert = NO;
549       
550       break;
551     }
552     
553     case JSTYPE_NUMBER:
554       if (JSVAL_IS_INT(*(jsval *)_value)) {
555         int32 i;
556         
557         if ((couldConvert = JS_ValueToInt32(cx, *(jsval *)_value, &i)))
558           return [NSNumber numberWithInt:i];
559       }
560       else {
561         jsdouble d;
562
563         if ((couldConvert = JS_ValueToNumber(cx, *(jsval *)_value, &d)))
564           return [NSNumber numberWithDouble:d];
565       }
566       break;
567       
568     case JSTYPE_BOOLEAN: {
569       JSBool b;
570       
571       couldConvert = JS_ValueToBoolean(cx, *(jsval *)_value, &b);
572       if (couldConvert)
573         return [NSNumber numberWithBool:b ? YES : NO];
574       break;
575     }
576
577     default:
578       [NSException raise:@"InvalidJavaScriptTypeException"
579                    format:@"JavaScript value has unknown type %i !", jsType];
580   }
581   
582   if (!couldConvert) {
583       [NSException raise:@"JavaScriptTypeConvertException"
584                    format:@"Could not convert JavaScript value of type %i !",
585                      jsType];
586   }
587   
588   return nil;
589 }
590
591 - (BOOL)jsValue:(void *)_value forObject:(id)_obj {
592   /*
593     This is used to convert ObjC object _obj to a JSVAL.
594     
595     Cases:
596     - _obj is a proxy for a JavaScript object (eg NGJavaScriptObject),
597       the proxy will return the value itself
598     - _obj is a primitive Foundation object (eg NSString), the object
599       will convert itself to a primitiv JavaScript type using
600       _jsGetValue:inJSContext:
601     - _obj is a complex object, it will be mapped to a proxy JSObject
602     
603     Primitive types seem to be broken in certain cases right now.
604   */
605   if (_obj == nil) {
606     *(jsval *)_value = JSVAL_NULL;
607     return YES;
608   }
609   
610   if ([_obj respondsToSelector:@selector(_jsGetValue:inJSContext:)]) {
611     if (logValueConversion) {
612       NSLog(@"%s(0x%08X, 0x%08X<%@>) => own handling ..", 
613               __PRETTY_FUNCTION__,
614               _value, _obj, NSStringFromClass([_obj class]));
615     }
616     /* eg this is called on NSString */
617     return [_obj _jsGetValue:_value inJSContext:self->jsContext];
618   }
619   else if (_value) {
620     JSObject *jso;
621
622     if (logValueConversion) {
623       NSLog(@"%s(0x%08X, 0x%08X<%@>) => get handle ..", 
624               __PRETTY_FUNCTION__,
625               _value, _obj, NSStringFromClass([_obj class]));
626     }
627     
628     if ((jso = [self handleForObject:_obj]) == NULL)
629       return NO;
630     
631     *((jsval *)_value) = OBJECT_TO_JSVAL(jso);
632     return YES;
633   }
634   else {
635     if (logValueConversion) {
636       NSLog(@"%s(0x%08X, 0x%08X<%@>) => missing value store ?", 
637               __PRETTY_FUNCTION__,
638               _value, _obj, NSStringFromClass([_obj class]));
639     }
640     return NO;
641   }
642 }
643
644 @end /* NGJavaScriptObjectMappingContext */
645
646 @implementation NGJavaScriptObjectMappingContext(CombinedObjects)
647
648 /* combined objects */
649
650 - (void)makeObjectCombined:(id)_object {
651   Class    clazz;
652   unsigned oldRC;
653   JSCombinedObjInfo *combinedObjInfo;
654   id handler;
655   
656   if (NSMapGet(combinedToInfo, _object))
657     /* object is already a combined one */
658     return;
659   
660   oldRC = [_object retainCount];
661   clazz = [_object class];
662   
663   handler = [[NGJavaScriptObjectHandler alloc]
664                                         initWithObject:_object
665                                         inMappingContext:self];
666   
667   if (![clazz isJSCombinedObjectClass]) {
668     // TODO: is this correct shouldn't we add combined behaviour only
669     //       *on* combined classes ??, explain !
670
671 #if NeXT_RUNTIME || APPLE_RUNTIME
672     NSLog(@"ERROR(%s): combined objects not supported on this runtime!",
673           __PRETTY_FUNCTION__);
674     /* TODO: port to MacOSX */
675 #else
676     static Class BehaviourClass = Nil;
677     BehaviourClass = NSClassFromString(@"JSCombinedObjectBehaviour");
678     NSAssert(BehaviourClass, @"did not find JSCombinedObjectBehaviour !");
679 #if GNUSTEP_BASE_LIBRARY
680     behavior_class_add_class(clazz, BehaviourClass);
681 #else
682     class_add_behavior(clazz, BehaviourClass);
683 #endif
684 #endif
685   }
686   
687   combinedObjInfo = calloc(1, sizeof(JSCombinedObjInfo));
688   combinedObjInfo->jso     = [handler handle];
689   combinedObjInfo->handler = handler;
690   combinedObjInfo->ctx     = self;
691   combinedObjInfo->rc      = oldRC; // -1 ???
692   
693   combinedObjInfo->rootRef = YES;
694   [handler jsRetain];
695   AUTORELEASE(handler);
696   
697   NSMapInsertKnownAbsent(combinedToInfo,  _object, combinedObjInfo);
698   
699   if (NGJavaScriptBridge_TRACK_MEMORY) {
700     NSLog(@"combine: o0x%08X<%@>->j0x%08X "
701           @"(handler=0x%08X, old-rc=%d, new-rc=%d)",
702           _object, NSStringFromClass([_object class]),
703           combinedObjInfo->jso, combinedObjInfo->handler, oldRC, [_object retainCount]);
704   }
705   
706 #if DEBUG
707   NSAssert([_object isJSCombinedObject], @"still not a combined object !");
708 #endif
709 }
710
711 - (BOOL)isCombinedObject:(id)_object {
712   return NSMapGet(combinedToInfo, _object) ? YES : NO;
713 }
714
715 - (void)_logCombinedObjects {
716   NSMapEnumerator e;
717   id                object;
718   JSCombinedObjInfo *combinedObjInfo;
719   
720   printf("Combined objects:\n");
721   printf("  %-10s %-16s %-2s %-10s %-10s %-10s %-2s %-2s\n",
722          "ObjC",
723          "Class",
724          "o#",
725          "JS",
726          "Ctx",
727          "Handler",
728          "/#",
729          "h#");
730   e = NSEnumerateMapTable(combinedToInfo);
731   while (NSNextMapEnumeratorPair(&e, (void*)&object, (void*)&combinedObjInfo)) {
732     printf("  0x%08X %-16s %2d 0x%08X 0x%08X 0x%08X %2d %2d\n", 
733            (unsigned)object, [NSStringFromClass([object class]) cString],
734            [object retainCount],
735            (unsigned)combinedObjInfo->jso,
736            (unsigned)combinedObjInfo->ctx,
737            (unsigned)combinedObjInfo->handler,
738            [combinedObjInfo->handler jsRootRetainCount],
739            [combinedObjInfo->handler retainCount]);
740   }
741 }
742
743 - (void)_jsFinalizeCombinedObject:(id)_object {
744   /*
745     This should never be called if ObjC RC > 0 !, since the ObjC object
746     keeps a root-ref to the JS object !
747   */
748   JSCombinedObjInfo *combinedObjInfo;
749   
750   if (_object == nil) return;
751   
752   if ((combinedObjInfo = NSMapGet(combinedToInfo, _object))) {
753     NSAssert(combinedObjInfo->ctx == self, @"invalid ctx for combined finalization !");
754     
755     if (combinedObjInfo->rc == 0) {
756       if (combinedObjInfo->rootRef) {
757         [combinedObjInfo->handler jsRelease];
758         combinedObjInfo->rootRef = NO;
759       }
760       
761       if (NGJavaScriptBridge_TRACK_MEMORY) {
762         NSLog(@"FREEING COMBINED OBJECT o%08X<%@>-j%08X (handler 0x%08X).",
763               _object, NSStringFromClass([_object class]),
764               combinedObjInfo->jso, combinedObjInfo->handler);
765       }
766       
767       NSMapRemove(combinedToInfo, _object);
768       combinedObjInfo = NULL;
769       
770       /* deallocate Objective-C memory of object */
771       [_object dealloc];
772     }
773     else {
774       NSLog(@"WARNING: finalized JS object, but handler RC > 0 !");
775     }
776   }
777 }
778
779 @end /* NGJavaScriptObjectMappingContext */
780
781 @implementation NSObject(JSCombinedObjects)
782
783 + (BOOL)isJSCombinedObjectClass {
784   return NO;
785 }
786
787 - (BOOL)isJSCombinedObject {
788   return NO;
789 }
790 - (NGJavaScriptObjectMappingContext *)jsObjectMappingContext {
791   return nil;
792 }
793
794 @end /* NSObject(JSCombinedObjects) */
795
796 @implementation JSCombinedObjectBehaviour
797
798 - (NGJavaScriptObjectMappingContext *)jsObjectMappingContext {
799   JSCombinedObjInfo *combinedObjInfo;
800   
801   if ((combinedObjInfo = NSMapGet(combinedToInfo, self)) == NULL)
802     return nil;
803   
804   return combinedObjInfo->ctx;
805 }
806
807 + (BOOL)isJSCombinedObjectClass {
808   return YES;
809 }
810
811 - (BOOL)isJSCombinedObject {
812   return NSMapGet(combinedToInfo, self) ? YES : NO;
813 }
814
815 /* retain-counting */
816
817 - (id)retain {
818   JSCombinedObjInfo *combinedObjInfo;
819   
820   if ((combinedObjInfo = NSMapGet(combinedToInfo, self)) == NULL) {
821     if (NGJavaScriptBridge_TRACK_NOINFO_MEMORY) {
822       NSLog(@"CO: NO INFO retain: o%08X<%@>, rc=%d",
823             self, NSStringFromClass([self class]), [self retainCount]);
824     }
825     return [super retain];
826   }
827   
828   if (combinedObjInfo->handler == nil) {
829     if (NGJavaScriptBridge_TRACK_MEMORY) {
830       NSLog(@"CO: NO HANDLER retain: o%08X<%@>-j0x%08X, rc=%d",
831             self, NSStringFromClass([self class]),
832             combinedObjInfo->jso, [self retainCount]);
833     }
834     return [super retain];
835   }
836   
837   if (combinedObjInfo->rc == 0) {
838     /* life, but not specially retained (RC=1) */
839     
840     if (!combinedObjInfo->rootRef) {
841       /* ensure that the JS object is life */
842       [combinedObjInfo->handler jsRetain];
843       combinedObjInfo->rootRef = YES;
844     }
845   }
846   combinedObjInfo->rc++;
847   
848   if (NGJavaScriptBridge_TRACK_MEMORY_RC) {
849     NSLog(@"CO: retain: o%08X<%@>-j%08X (handler=0x%08X), rc=%d, root-rc=%d",
850           self, NSStringFromClass([self class]), combinedObjInfo->jso, combinedObjInfo->handler,
851           combinedObjInfo->rc, [combinedObjInfo->handler jsRootRetainCount]);
852   }
853   
854   return self;
855 }
856
857 - (oneway void)release {
858   JSCombinedObjInfo *combinedObjInfo;
859
860   if ((combinedObjInfo = NSMapGet(combinedToInfo, self)) == NULL) {
861     if (NGJavaScriptBridge_TRACK_NOINFO_MEMORY)
862       NSLog(@"CO: NO INFO release: o%08X, rc=%d", self, [self retainCount]);
863     
864     [super release];
865     return;
866   }
867   
868   if (combinedObjInfo->handler == nil) {
869     if (NGJavaScriptBridge_TRACK_MEMORY) {
870       NSLog(@"CO: NO HANDLER release: o%08X<%@>-j0x%08X, rc=%d",
871             self, NSStringFromClass([self class]),
872             combinedObjInfo->jso, [self retainCount]);
873     }
874     
875     [super release];
876     return;
877   }
878   
879   if (NGJavaScriptBridge_TRACK_MEMORY_RC) {
880     NSLog(@"CO: release: o%08X<%@>-j%08X (handler=0x%08X), rc=%d, root-rc=%d",
881           self, NSStringFromClass([self class]), combinedObjInfo->jso, combinedObjInfo->handler,
882           [self retainCount], [combinedObjInfo->handler jsRootRetainCount]);
883   }
884   NSAssert1(combinedObjInfo->handler,
885             @"missing handler for combined object 0x%08X ..", self);
886
887   
888   /*
889     this does never dealloc the ObjC object - the ObjC object is deallocated
890     in the JS destructor !
891   */
892
893   combinedObjInfo->rc--;
894   
895   if (combinedObjInfo->rc == 0) {
896     /* not specially retained in the ObjC side anymore */
897     
898     /* JS object is still live, release our root-ref .. */
899     NSAssert(combinedObjInfo->rootRef, @"missing JS root-reference");
900     [combinedObjInfo->handler jsRelease];
901     combinedObjInfo->rootRef = NO;
902     
903     if (NGJavaScriptBridge_TRACK_MEMORY) {
904       NSLog(@"%s: released last ObjC reference of o%08X-j%08X, %d root-refs ..",
905             __PRETTY_FUNCTION__,
906             self, combinedObjInfo->jso, [combinedObjInfo->handler jsRootRetainCount]);
907     }
908     
909     [combinedObjInfo->ctx performSelector:@selector(collectGarbage)
910                 withObject:nil
911                 afterDelay:0.0];
912   }
913 }
914
915 - (unsigned)retainCount {
916   JSCombinedObjInfo *combinedObjInfo;
917   
918   if ((combinedObjInfo = NSMapGet(combinedToInfo, self)) == NULL)
919     return [super retainCount];
920   
921   if (combinedObjInfo->handler == nil)
922     return [super retainCount];
923
924   return combinedObjInfo->rc;
925 }
926
927 /* evaluation */
928
929 - (id)evaluateScript:(NSString *)_js language:(NSString *)_language {
930   JSCombinedObjInfo *combinedObjInfo;
931   
932   if ((combinedObjInfo = NSMapGet(combinedToInfo, self)) == NULL) {
933     /* what to do ? */
934     return [[[NGJavaScriptObjectMappingContext activeObjectMappingContext]
935                                                handlerForObject:self]
936                                                evaluateScript:_js];
937   }
938   
939   return [combinedObjInfo->handler evaluateScript:_js];
940 }
941 - (id)evaluateJavaScript:(NSString *)_js {
942   /* deprecated */
943   return [self evaluateScript:_js language:@"javascript"];
944 }
945
946 @end /* JSCombinedObjectBehaviour */