Add header.
[kai/samba.git] / source4 / lib / appweb / ejs-2.0 / ejs / classes / ejsObject.c
1 /*
2  *      @file   ejsObject.c
3  *      @brief  Object class
4  */
5 /********************************* Copyright **********************************/
6 /*
7  *      @copy   default
8  *      
9  *      Copyright (c) Mbedthis Software LLC, 2003-2006. All Rights Reserved.
10  *      Copyright (c) Michael O'Brien, 1994-1995. All Rights Reserved.
11  *      
12  *      This software is distributed under commercial and open source licenses.
13  *      You may use the GPL open source license described below or you may acquire 
14  *      a commercial license from Mbedthis Software. You agree to be fully bound 
15  *      by the terms of either license. Consult the LICENSE.TXT distributed with 
16  *      this software for full details.
17  *      
18  *      This software is open source; you can redistribute it and/or modify it 
19  *      under the terms of the GNU General Public License as published by the 
20  *      Free Software Foundation; either version 2 of the License, or (at your 
21  *      option) any later version. See the GNU General Public License for more 
22  *      details at: http://www.mbedthis.com/downloads/gplLicense.html
23  *      
24  *      This program is distributed WITHOUT ANY WARRANTY; without even the 
25  *      implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
26  *      
27  *      This GPL license does NOT permit incorporating this software into 
28  *      proprietary programs. If you are unable to comply with the GPL, you must
29  *      acquire a commercial license to use this software. Commercial licenses 
30  *      for this software and support services are available from Mbedthis 
31  *      Software at http://www.mbedthis.com 
32  *      
33  *      @end
34  */
35 /********************************** Includes **********************************/
36
37 #include        "ejs.h"
38
39 #if BLD_FEATURE_EJS
40
41 /****************************** Forward Declarations **************************/
42 /*
43  *      Support routines
44  */
45
46 static void formatVar(Ejs *ep, MprBuf *bp, EjsVar *vp);
47
48 /******************************************************************************/
49 /*
50  *      Routine to create an object of the desired class. Class name may 
51  *      contain "." 
52  *
53  *      The created object will be a stand-alone class NOT entered into the 
54  *      properties of any other object. Callers must do this if required. ClassName 
55  *      may contain "." and is interpreted relative to "obj" if supplied.
56  *
57  *      Note: this does not call the constructors for the various objects and base
58  *      classes.
59  */
60
61 EjsVar *ejsCreateSimpleObjInternal(EJS_LOC_DEC(ep, loc), const char *className)
62 {
63         EjsVar  *baseClass;
64
65         if (className && *className) {
66                 baseClass = ejsGetClass(ep, 0, className);
67                 if (baseClass == 0) {
68                         mprError(ep, MPR_LOC, "Can't find base class %s", className);
69                         return 0;
70                 }
71         } else {
72                 baseClass = 0;
73         }
74
75         return ejsCreateSimpleObjUsingClassInt(EJS_LOC_PASS(ep, loc), 
76                 baseClass);
77 }
78
79 /******************************************************************************/
80 /*
81  *      Create an object based upon the specified base class object. It will be a 
82  *      stand-alone class not entered into the properties of any other object. 
83  *      Callers must do this if required. 
84  *
85  *      Note: this does not call the constructors for the various objects and base
86  *      classes.
87  */
88
89 EjsVar *ejsCreateSimpleObjUsingClassInt(EJS_LOC_DEC(ep, loc), 
90         EjsVar *baseClass)
91 {
92         EjsVar          *vp;
93
94         mprAssert(baseClass);
95
96         if (baseClass == 0) {
97                 mprError(ep, MPR_LOC, "Missing base class\n");
98                 return 0;
99         }
100
101         vp = ejsCreateObjVarInternal(EJS_LOC_PASS(ep, loc));
102         if (vp == 0) {
103                 return vp;
104         }
105
106         ejsSetBaseClass(vp, baseClass);
107
108         /*
109          *      This makes all internal method accesses faster
110          *      NOTE: this code is duplicated in ejsCreateSimpleClass
111          */
112         mprAssert(vp->objectState);
113         vp->objectState->methods = baseClass->objectState->methods;
114
115         return vp;
116 }
117
118 /******************************************************************************/
119
120 void ejsSetMethods(Ejs *ep, EjsVar *op)
121 {
122         op->objectState->methods = ep->global->objectState->methods;
123 }
124
125 /******************************************************************************/
126 /******************************** Internal Methods ****************************/
127 /******************************************************************************/
128
129 static EjsVar *createObjProperty(Ejs *ep, EjsVar *obj, const char *property)
130 {
131         return ejsGetVarPtr(ejsCreateSimpleProperty(ep, obj, property));
132 }
133
134 /******************************************************************************/
135
136 static int deleteObjProperty(Ejs *ep, EjsVar *obj, const char *property)
137 {
138         return ejsDeleteProperty(ep, obj, property);
139 }
140
141 /******************************************************************************/
142
143 static EjsVar *getObjProperty(Ejs *ep, EjsVar *obj, const char *property)
144 {
145         return ejsGetVarPtr(ejsGetSimpleProperty(ep, obj, property));
146 }
147
148 /******************************************************************************/
149 /*
150  *      Set the value of a property. Create if it does not exist
151  */
152
153 static EjsVar *setObjProperty(Ejs *ep, EjsVar *obj, const char *property, 
154         const EjsVar *value)
155 {
156         EjsProperty             *pp;
157         EjsVar                  *vp;
158
159         pp = ejsCreateSimpleProperty(ep, obj, property);
160         if (pp == 0) {
161                 mprAssert(pp);
162                 return 0;
163         }
164         vp = ejsGetVarPtr(pp);
165         if (ejsWriteVar(ep, vp, value, EJS_SHALLOW_COPY) < 0) {
166                 mprAssert(0);
167                 return 0;
168         }
169         return ejsGetVarPtr(pp);
170 }
171
172 /******************************************************************************/
173 /*********************************** Constructors *****************************/
174 /******************************************************************************/
175 #if UNUSED
176 /*
177  *      Object constructor. We don't use this for speed. Think very carefully if
178  *      you add an object constructor.
179  */
180
181 int ejsObjectConstructor(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv)
182 {
183         return 0;
184 }
185
186 #endif
187 /******************************************************************************/
188 /******************************** Visible Methods *****************************/
189 /******************************************************************************/
190
191 static int cloneMethod(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv)
192 {
193         int             copyDepth;
194
195         copyDepth = EJS_DEEP_COPY;
196
197         if (argc == 1 && ejsVarToBoolean(argv[0])) {
198                 copyDepth =  EJS_RECURSIVE_DEEP_COPY;
199         }
200
201         ejsWriteVar(ep, ep->result, thisObj, copyDepth);
202
203         return 0;
204 }
205
206 /******************************************************************************/
207
208 static int toStringMethod(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv)
209 {
210         MprBuf  *bp;
211         int             saveMaxDepth, saveDepth, saveFlags;
212
213         saveMaxDepth = ep->maxDepth;
214
215         if (argc >= 1) {
216                 ep->maxDepth = ejsVarToInteger(argv[0]);
217         } else if (ep->maxDepth == 0) {
218                 ep->maxDepth = MAXINT;
219         }
220
221         saveFlags = ep->flags;
222         if (argc >= 2) {
223                 if (ejsVarToBoolean(argv[1])) {
224                         ep->flags |= EJS_FLAGS_ENUM_HIDDEN;
225                 }
226         }
227         if (argc == 3) {
228                 if (ejsVarToBoolean(argv[2])) {
229                         ep->flags |= EJS_FLAGS_ENUM_BASE;
230                 }
231         }
232
233         bp = mprCreateBuf(ep, 0, 0);
234
235         saveDepth = ep->depth;
236
237         formatVar(ep, bp, thisObj);
238
239         ep->depth = saveDepth;
240         ep->maxDepth = saveMaxDepth;
241
242         mprAddNullToBuf(bp);
243
244         ejsWriteVarAsString(ep, ep->result, mprGetBufStart(bp));
245         mprFree(bp);
246
247         ep->flags = saveFlags;
248
249         return 0;
250 }
251
252 /******************************************************************************/
253
254 static int valueOfMethod(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv)
255 {
256         if (argc != 0) {
257                 mprAssert(0);
258                 return -1;
259         }
260
261         switch (thisObj->type) {
262         default:
263         case EJS_TYPE_UNDEFINED:
264         case EJS_TYPE_NULL:
265         case EJS_TYPE_CMETHOD:
266         case EJS_TYPE_OBJECT:
267         case EJS_TYPE_METHOD:
268         case EJS_TYPE_STRING_CMETHOD:
269                 ejsWriteVar(ep, ep->result, thisObj, EJS_SHALLOW_COPY);
270                 break;
271
272         case EJS_TYPE_STRING:
273                 ejsWriteVarAsInteger(ep, ep->result, atoi(thisObj->string));
274                 break;
275
276         case EJS_TYPE_BOOL:
277         case EJS_TYPE_INT:
278 #if BLD_FEATURE_INT64
279         case EJS_TYPE_INT64:
280 #endif
281 #if BLD_FEATURE_FLOATING_POINT
282         case EJS_TYPE_FLOAT:
283 #endif
284                 ejsWriteVar(ep, ep->result, thisObj, EJS_SHALLOW_COPY);
285                 break;
286         } 
287         return 0;
288 }
289
290 /******************************************************************************/
291
292 static int hashGetAccessor(Ejs *ejs, EjsVar *thisObj, int argc, EjsVar **argv)
293 {
294         ejsSetReturnValueToInteger(ejs, (int) thisObj->objectState);
295         return 0;
296 }
297
298 /******************************************************************************/
299
300 static int classGetAccessor(Ejs *ejs, EjsVar *thisObj, int argc, EjsVar **argv)
301 {
302         if (thisObj->objectState == 0 || thisObj->objectState->baseClass == 0) {
303                 ejsSetReturnValueToString(ejs, "object");
304         } else {
305                 ejsSetReturnValueToString(ejs, 
306                         thisObj->objectState->baseClass->objectState->className);
307         }
308         return 0;
309 }
310
311 /******************************************************************************/
312 /*
313  *      Format an object. Called recursively to format properties and contained 
314  *      objects.
315  */
316
317 static void formatVar(Ejs *ep, MprBuf *bp, EjsVar *vp)
318 {
319         EjsProperty     *pp, *first;
320         EjsVar          *propVar, *baseClass;
321         char            *buf, *value;
322         int                     i;
323
324         if (vp->type == EJS_TYPE_OBJECT) {
325                 if (!vp->objectState->visited) {
326
327                         mprPutStringToBuf(bp, vp->isArray ? "[\n" : "{\n");
328
329                         ep->depth++;
330                         vp->objectState->visited = 1;
331
332                         if (ep->depth <= ep->maxDepth) {
333                                 first = ejsGetFirstProperty(vp, EJS_ENUM_ALL);
334
335                                 if (ep->flags & EJS_FLAGS_ENUM_BASE) {
336                                         baseClass = vp->objectState->baseClass;
337                                         if (baseClass) {
338                                                 for (i = 0; i < ep->depth; i++) {
339                                                         mprPutStringToBuf(bp, "  ");
340                                                 }
341                                                 mprPutStringToBuf(bp, baseClass->objectState->objName);
342                                                 mprPutStringToBuf(bp, ": /* Base Class */ ");
343                                                 if (baseClass->objectState == vp->objectState) {
344                                                         value = "this";
345                                                 } else if (ejsRunMethodCmd(ep, baseClass, "toString", 
346                                                                 "%d", ep->maxDepth) < 0) {
347                                                         value = "[object Object]";
348                                                 } else {
349                                                         mprAssert(ejsVarIsString(ep->result));
350                                                         value = ep->result->string;
351                                                 }
352                                                 mprPutStringToBuf(bp, value);
353                                                 if (first) {
354                                                         mprPutStringToBuf(bp, ",\n");
355                                                 }
356                                         }
357                                 }
358
359                                 pp = first;
360                                 while (pp) {
361                                         if (! pp->dontEnumerate || 
362                                                         ep->flags & EJS_FLAGS_ENUM_HIDDEN) {
363                                                 for (i = 0; i < ep->depth; i++) {
364                                                         mprPutStringToBuf(bp, "  ");
365                                                 }
366
367                                                 if (! vp->isArray) {
368                                                         mprPutStringToBuf(bp, pp->name);
369                                                         mprPutStringToBuf(bp, ": ");
370                                                 }
371
372                                                 propVar = ejsGetVarPtr(pp);
373                                                 if (propVar->type == EJS_TYPE_OBJECT) {
374                                                         if (pp->var.objectState == vp->objectState) {
375                                                                 value = "this";
376                                                         } else if (ejsRunMethodCmd(ep, propVar, 
377                                                                         "toString", "%d", ep->maxDepth) < 0) {
378                                                                 value = "[object Object]";
379                                                         } else {
380                                                                 mprAssert(ejsVarIsString(ep->result));
381                                                                 value = ep->result->string;
382                                                         }
383                                                         mprPutStringToBuf(bp, value);
384
385                                                 } else {
386                                                         formatVar(ep, bp, &pp->var);
387                                                 }
388
389                                                 pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
390                                                 if (pp) {
391                                                         mprPutStringToBuf(bp, ",\n");
392                                                 }
393                                         } else {
394                                                 pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
395                                         }
396                                 }
397                         }
398                         vp->objectState->visited = 0;
399
400                         mprPutCharToBuf(bp, '\n');
401
402                         ep->depth--;
403                         for (i = 0; i < ep->depth; i++) {
404                                 mprPutStringToBuf(bp, "  ");
405                         }
406                         mprPutCharToBuf(bp, vp->isArray ? ']' : '}');
407                 }
408
409         } else if (vp->type == EJS_TYPE_METHOD) {
410
411                 mprPutStringToBuf(bp, "function (");
412                 for (i = 0; i < vp->method.args->length; i++) {
413                         mprPutStringToBuf(bp, vp->method.args->items[i]);
414                         if ((i + 1) < vp->method.args->length) {
415                                 mprPutStringToBuf(bp, ", ");
416                         }
417                 }
418                 mprPutStringToBuf(bp, ") {");
419                 mprPutStringToBuf(bp, vp->method.body);
420                 for (i = 0; i < ep->depth; i++) {
421                         mprPutStringToBuf(bp, "  ");
422                 }
423                 mprPutStringToBuf(bp, "}");
424
425         } else {
426
427                 if (vp->type == EJS_TYPE_STRING) {
428                         mprPutCharToBuf(bp, '\"');
429                 }
430
431                 /*
432                  *      We don't use ejsVarToString for arrays, objects and strings.
433                  *      This is because ejsVarToString does not call "obj.toString"
434                  *      and it is not required for strings.
435                  *      MOB - rc
436                  */
437                 buf = ejsVarToString(ep, vp);
438                 mprPutStringToBuf(bp, buf);
439
440                 if (vp->type == EJS_TYPE_STRING) {
441                         mprPutCharToBuf(bp, '\"');
442                 }
443         }
444 }
445
446 /******************************************************************************/
447 /*
448  *      mixin code. Blends code at the "thisObj" level.
449  */ 
450
451 static int mixinMethod(Ejs *ep, EjsVar *thisObj, int argc, EjsVar **argv)
452 {
453         EjsProperty     *pp;
454         char            *buf;
455         int                     fid, i, rc;
456
457         mprAssert(argv);
458
459         /*
460          *      Create a variable scope block set to the current object
461          */
462         rc = 0;
463         fid = ejsSetBlock(ep, thisObj);
464
465         for (i = 0; i < argc; i++) {
466
467                 if (ejsVarIsString(argv[i])) {
468                         rc = ejsEvalScript(ep, argv[i]->string, 0);
469
470                 }  else if (ejsVarIsObject(argv[i])) {
471
472                         /*      MOB -- OPT. When we have proper scope chains, we should just
473                                 refer to the module and not copy */
474                         pp = ejsGetFirstProperty(argv[i], EJS_ENUM_ALL);
475                         while (pp) {
476                                 ejsSetProperty(ep, thisObj, pp->name, ejsGetVarPtr(pp));
477                                 pp = ejsGetNextProperty(pp, EJS_ENUM_ALL);
478                         }
479
480                 } else {
481                         /* MOB - rc */
482                         buf = ejsVarToString(ep, argv[i]);
483                         rc = ejsEvalScript(ep, buf, 0);
484
485                 }
486                 if (rc < 0) {
487                         ejsCloseBlock(ep, fid);
488                         return -1;
489                 }
490         } 
491         ejsCloseBlock(ep, fid);
492         return 0;
493 }
494
495 /******************************************************************************/
496 /*
497  *      Create the object class
498  */
499
500 int ejsDefineObjectClass(Ejs *ep)
501 {
502         EjsMethods      *methods;
503         EjsProperty     *objectProp, *protoProp;
504         EjsVar          *op, *globalClass;
505
506         /*
507          *      Must specially hand-craft the object class as it is the base class
508          *      of all objects.
509          */
510         op = ejsCreateObjVar(ep);
511         if (op == 0) {
512                 return MPR_ERR_CANT_CREATE;
513         }
514         ejsSetClassName(ep, op, "Object");
515
516         /*
517          *      Don't use a constructor for objects for speed
518          */
519         ejsMakeClassNoConstructor(op);
520
521         /*
522          *      MOB -- should mark properties as public / private and class or instance.
523          */
524         ejsDefineCMethod(ep, op, "clone", cloneMethod, EJS_NO_LOCAL);
525         ejsDefineCMethod(ep, op, "toString", toStringMethod, EJS_NO_LOCAL);
526         ejsDefineCMethod(ep, op, "valueOf", valueOfMethod, EJS_NO_LOCAL);
527         ejsDefineCMethod(ep, op, "mixin", mixinMethod, EJS_NO_LOCAL);
528
529         ejsDefineCAccessors(ep, op, "hash", hashGetAccessor, 0, EJS_NO_LOCAL);
530         ejsDefineCAccessors(ep, op, "baseClass", classGetAccessor, 0, EJS_NO_LOCAL);
531
532         /*
533          *      MOB -- make this an accessor
534          */
535         protoProp = ejsSetProperty(ep, op, "prototype", op);
536         if (protoProp == 0) {
537                 ejsFreeVar(ep, op);
538                 return MPR_ERR_CANT_CREATE;
539         }
540
541         /*
542          *      Setup the internal methods. Most classes will never override these.
543          *      The XML class will. We rely on talloc to free internal. Use "ep" as
544          *      the parent as we need "methods" to live while the interpreter lives.
545          */
546         methods = mprAllocTypeZeroed(ep, EjsMethods);
547         op->objectState->methods = methods;
548
549         methods->createProperty = createObjProperty;
550         methods->deleteProperty = deleteObjProperty;
551         methods->getProperty = getObjProperty;
552         methods->setProperty = setObjProperty;
553
554         objectProp = ejsSetPropertyAndFree(ep, ep->global, "Object", op);
555
556         /*
557          *      Change the global class to use Object's methods 
558          */
559         globalClass = ep->service->globalClass;
560         globalClass->objectState->methods = methods;
561         globalClass->objectState->baseClass = ejsGetVarPtr(protoProp);
562
563         ep->objectClass = ejsGetVarPtr(objectProp);
564
565         if (ejsObjHasErrors(ejsGetVarPtr(objectProp))) {
566                 ejsFreeVar(ep, op);
567                 return MPR_ERR_CANT_CREATE;
568         }
569         return 0;
570 }
571
572 /******************************************************************************/
573
574 #else
575 void ejsObjectDummy() {}
576
577 /******************************************************************************/
578 #endif /* BLD_FEATURE_EJS */
579
580 /*
581  * Local variables:
582  * tab-width: 4
583  * c-basic-offset: 4
584  * End:
585  * vim:tw=78
586  * vim600: sw=4 ts=4 fdm=marker
587  * vim<600: sw=4 ts=4
588  */