2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / IdScriptable.java
1 /* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-\r
2  *\r
3  * The contents of this file are subject to the Netscape Public\r
4  * License Version 1.1 (the "License"); you may not use this file\r
5  * except in compliance with the License. You may obtain a copy of\r
6  * the License at http://www.mozilla.org/NPL/\r
7  *\r
8  * Software distributed under the License is distributed on an "AS\r
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr\r
10  * implied. See the License for the specific language governing\r
11  * rights and limitations under the License.\r
12  *\r
13  * The Original Code is Rhino code, released\r
14  * May 6, 1999.\r
15  *\r
16  * The Initial Developer of the Original Code is Netscape\r
17  * Communications Corporation.  Portions created by Netscape are\r
18  * Copyright (C) 1997-1999 Netscape Communications Corporation. All\r
19  * Rights Reserved.\r
20  *\r
21  * Contributor(s):\r
22  * Igor Bukanov\r
23  *\r
24  * Alternatively, the contents of this file may be used under the\r
25  * terms of the GNU Public License (the "GPL"), in which case the\r
26  * provisions of the GPL are applicable instead of those above.\r
27  * If you wish to allow use of your version of this file only\r
28  * under the terms of the GPL and not to allow others to use your\r
29  * version of this file under the NPL, indicate your decision by\r
30  * deleting the provisions above and replace them with the notice\r
31  * and other provisions required by the GPL.  If you do not delete\r
32  * the provisions above, a recipient may use your version of this\r
33  * file under either the NPL or the GPL.\r
34  */\r
35 \r
36 package org.mozilla.javascript;\r
37 \r
38 /**\r
39 Base class for native object implementation that uses IdFunction to export its methods to script via <class-name>.prototype object.\r
40 \r
41 Any descendant should implement at least the following methods:\r
42     mapNameToId\r
43     getIdName\r
44     execMethod\r
45     methodArity\r
46 \r
47 To define non-function properties, the descendant should customize\r
48     getIdValue\r
49     setIdValue\r
50     getIdDefaultAttributes\r
51     maxInstanceId\r
52 to get/set property value and provide its default attributes.\r
53 \r
54 To customize initializition of constructor and protype objects, descendant\r
55 may override scopeInit or fillConstructorProperties methods.\r
56 \r
57 */\r
58 public abstract class IdScriptable extends ScriptableObject\r
59     implements IdFunctionMaster\r
60 {\r
61     /** NULL_TAG can be used to distinguish between uninitialized and null\r
62      ** values\r
63      */\r
64     protected static final Object NULL_TAG = new Object();\r
65 \r
66     public IdScriptable() {\r
67         activateIdMap(maxInstanceId());\r
68     }\r
69     \r
70     public boolean has(String name, Scriptable start) {\r
71         if (maxId != 0) {\r
72             int id = mapNameToId(name);\r
73             if (id != 0) {\r
74                 return hasValue(id);\r
75             }\r
76         }\r
77         return super.has(name, start);\r
78     }\r
79 \r
80     public Object get(String name, Scriptable start) {\r
81         if (CACHE_NAMES) {\r
82             int maxId = this.maxId;\r
83             L:if (maxId != 0) {\r
84                 Object[] data = idMapData;\r
85                 if (data == null) { \r
86                     int id = mapNameToId(name);\r
87                     if (id != 0) {\r
88                         return getIdValue(id);\r
89                     }\r
90                 }\r
91                 else {\r
92                     int id = lastIdCache;\r
93                     if (data[id - 1 + maxId] != name) {\r
94                         id = mapNameToId(name);\r
95                         if (id == 0) { break L; }\r
96                            data[id - 1 + maxId] = name;\r
97                            lastIdCache = id;\r
98                     }\r
99                     Object value = data[id - 1];\r
100                     if (value == null) {\r
101                         value = getIdValue(id);\r
102                     }\r
103                     else if (value == NULL_TAG) {\r
104                         value = null;\r
105                     }\r
106                     return value;\r
107                 }\r
108             }\r
109         }\r
110         else {\r
111             if (maxId != 0) {\r
112                 int id = mapNameToId(name);\r
113                 if (id != 0) {\r
114                     Object[] data = idMapData;\r
115                     if (data == null) { \r
116                         return getIdValue(id);\r
117                     }\r
118                     else {\r
119                         Object value = data[id - 1];\r
120                         if (value == null) {\r
121                             value = getIdValue(id);\r
122                         }\r
123                         else if (value == NULL_TAG) {\r
124                             value = null;\r
125                         }\r
126                         return value;\r
127                     }\r
128                 }\r
129             }\r
130         }\r
131         return super.get(name, start);\r
132     }\r
133 \r
134     public void put(String name, Scriptable start, Object value) {\r
135         if (maxId != 0) {\r
136             int id = mapNameToId(name);\r
137             if (id != 0) {\r
138                 int attr = getAttributes(id);\r
139                 if ((attr & READONLY) == 0) {\r
140                     if (start == this) {\r
141                         setIdValue(id, value);\r
142                     }\r
143                     else {\r
144                         start.put(name, start, value);\r
145                     }\r
146                 }\r
147                 return;\r
148             }\r
149         }\r
150         super.put(name, start, value);\r
151     }\r
152 \r
153     public void delete(String name) {\r
154         if (maxId != 0) {\r
155             int id = mapNameToId(name);\r
156             if (id != 0) {\r
157                 // Let the super class to throw exceptions for sealed objects\r
158                 if (!isSealed()) {\r
159                     int attr = getAttributes(id);\r
160                     if ((attr & PERMANENT) == 0) {\r
161                         deleteIdValue(id);\r
162                     }\r
163                     return;\r
164                 }\r
165             }\r
166         }\r
167         super.delete(name);\r
168     }\r
169 \r
170     public int getAttributes(String name, Scriptable start)\r
171         throws PropertyException\r
172     {\r
173         if (maxId != 0) {\r
174             int id = mapNameToId(name);\r
175             if (id != 0) {\r
176                 if (hasValue(id)) {\r
177                     return getAttributes(id);\r
178                 }\r
179                 // For ids with deleted values super will throw exceptions\r
180             }\r
181         }\r
182         return super.getAttributes(name, start);\r
183     }\r
184 \r
185     public void setAttributes(String name, Scriptable start,\r
186                               int attributes)\r
187         throws PropertyException\r
188     {\r
189         if (maxId != 0) {\r
190             int id = mapNameToId(name);\r
191             if (id != 0) {\r
192                 if (hasValue(id)) {\r
193                     synchronized (this) {\r
194                         setAttributes(id, attributes);\r
195                     }\r
196                     return;\r
197                 }\r
198                 // For ids with deleted values super will throw exceptions\r
199             }\r
200         }\r
201         super.setAttributes(name, start, attributes);\r
202     }\r
203 \r
204     synchronized void addPropertyAttribute(int attribute) {\r
205         extraIdAttributes |= (byte)attribute;\r
206         super.addPropertyAttribute(attribute);\r
207     }\r
208 \r
209     /**\r
210      * Redefine ScriptableObject.defineProperty to allow changing\r
211      * values/attributes of id-based properties unless \r
212      * getIdDefaultAttributes contains the READONLY attribute.\r
213      * @see #getIdDefaultAttributes\r
214      * @see org.mozilla.javascript.ScriptableObject#defineProperty\r
215      */\r
216     public void defineProperty(String propertyName, Object value,\r
217                                int attributes)\r
218     {\r
219         if (maxId != 0) {\r
220             int id = mapNameToId(propertyName);\r
221             if (id != 0) {\r
222                 int default_attributes = getIdDefaultAttributes(id);\r
223                 if ((default_attributes & READONLY) != 0) {\r
224                     // It is a bug to redefine id with readonly attributes\r
225                     throw new RuntimeException\r
226                         ("Attempt to redefine read-only id " + propertyName);\r
227                 }\r
228                 setAttributes(id, attributes);\r
229                 setIdValue(id, value);\r
230                 return;\r
231             }\r
232         }\r
233         super.defineProperty(propertyName, value, attributes);\r
234     }\r
235 \r
236     Object[] getIds(boolean getAll) {\r
237         Object[] result = super.getIds(getAll);\r
238         \r
239         if (maxId != 0) {\r
240             Object[] ids = null;\r
241             int count = 0;\r
242             \r
243             for (int id = maxId; id != 0; --id) {\r
244                 if (hasValue(id)) {\r
245                     if (getAll || (getAttributes(id) & DONTENUM) == 0) {\r
246                         if (count == 0) {\r
247                             // Need extra room for nor more then [1..id] names\r
248                             ids = new Object[id];\r
249                         }\r
250                         ids[count++] = getIdName(id);\r
251                     }\r
252                 }\r
253             }\r
254             if (count != 0) {\r
255                 if (result.length == 0 && ids.length == count) {\r
256                     result = ids;\r
257                 }\r
258                 else {\r
259                     Object[] tmp = new Object[result.length + count];\r
260                     System.arraycopy(result, 0, tmp, 0, result.length);\r
261                     System.arraycopy(ids, 0, tmp, result.length, count);\r
262                     result = tmp;\r
263                 }\r
264             }\r
265         }\r
266         return result;\r
267     }\r
268 \r
269     /** Return maximum id number that should be present in each instance. */\r
270     protected int maxInstanceId() { return 0; }\r
271 \r
272     /**\r
273      * Map name to id of prototype or instance property.\r
274      * Should return 0 if not found\r
275      */\r
276     protected abstract int mapNameToId(String name);\r
277 \r
278     /** Map id back to property name it defines.\r
279      */\r
280     protected abstract String getIdName(int id);\r
281 \r
282     /** Get default attributes for id. \r
283      ** Default implementation return DONTENUM that is the standard attribute \r
284      ** for core EcmaScript function. Typically descendants need to overwrite\r
285      ** this for non-function attributes like length to return\r
286      ** DONTENUM | READONLY | PERMANENT or DONTENUM | PERMANENT\r
287      */\r
288     protected int getIdDefaultAttributes(int id) {\r
289         return DONTENUM;\r
290     }\r
291 \r
292     /** Check if id value exists.\r
293      ** Default implementation always returns true */\r
294     protected boolean hasIdValue(int id) {\r
295         return true;\r
296     }\r
297 \r
298     /** Get id value. \r
299      ** If id value is constant, descendant can call cacheIdValue to store\r
300      ** value in the permanent cache.\r
301      ** Default implementation creates IdFunction instance for given id\r
302      ** and cache its value\r
303      */\r
304     protected Object getIdValue(int id) {\r
305         IdFunction f = newIdFunction(id);\r
306         f.setParentScope(getParentScope());\r
307         return cacheIdValue(id, f);\r
308     }\r
309 \r
310     /**\r
311      * Set id value. \r
312      * IdScriptable never calls this method if result of\r
313      * <code>getIdDefaultAttributes(id)</code> contains READONLY attribute.\r
314      * Descendants can overwrite this method to provide custom handler for\r
315      * property assignments.\r
316      */\r
317     protected void setIdValue(int id, Object value) {\r
318         synchronized (this) {\r
319             ensureIdData()[id - 1] = (value != null) ? value : NULL_TAG;\r
320         }\r
321     }\r
322     \r
323     /**\r
324      * Store value in permanent cache unless value was already assigned to id.\r
325      * After this call IdScriptable never calls hasIdValue and getIdValue \r
326      * for the given id.\r
327      */\r
328     protected Object cacheIdValue(int id, Object value) {\r
329         synchronized (this) {\r
330             Object[] data = ensureIdData();\r
331             Object curValue = data[id - 1];\r
332             if (curValue == null) {\r
333                 data[id - 1] = (value != null) ? value : NULL_TAG;\r
334             }\r
335             else {\r
336                 value = curValue;\r
337             }\r
338         }\r
339         return value;\r
340     }\r
341     \r
342     /**\r
343      * Delete value represented by id so hasIdValue return false. \r
344      * IdScriptable never calls this method if result of\r
345      * <code>getIdDefaultAttributes(id)</code> contains PERMANENT attribute.\r
346      * Descendants can overwrite this method to provide custom handler for\r
347      * property delete.\r
348      */\r
349     protected void deleteIdValue(int id) {\r
350         synchronized (this) {\r
351             ensureIdData()[id - 1] = NOT_FOUND;\r
352         }\r
353     }\r
354     \r
355     /** 'thisObj' will be null if invoked as constructor, in which case\r
356      ** instance of Scriptable should be returned. */\r
357     public Object execMethod(int methodId, IdFunction function,\r
358                              Context cx, Scriptable scope,\r
359                              Scriptable thisObj, Object[] args)\r
360         throws JavaScriptException\r
361     {\r
362         throw IdFunction.onBadMethodId(this, methodId);\r
363     }\r
364 \r
365     /** Get arity or defined argument count for method with given id. \r
366      ** Should return -1 if methodId is not known or can not be used\r
367      ** with execMethod call. */\r
368     public int methodArity(int methodId) {\r
369         return -1;\r
370     }\r
371     \r
372     /** Activate id support with the given maximum id */\r
373     protected void activateIdMap(int maxId) {\r
374         this.maxId = maxId;\r
375     }\r
376     \r
377     /** Sets whether newly constructed function objects should be sealed */\r
378     protected void setSealFunctionsFlag(boolean sealed) {\r
379         setSetupFlag(SEAL_FUNCTIONS_FLAG, sealed);\r
380     }\r
381     \r
382     /** \r
383      * Set parameters of function properties. \r
384      * Currently only determines whether functions should use dynamic scope.\r
385      * @param cx context to read function parameters.\r
386      * \r
387      * @see org.mozilla.javascript.Context#hasCompileFunctionsWithDynamicScope\r
388      */\r
389     protected void setFunctionParametrs(Context cx) {\r
390         setSetupFlag(USE_DYNAMIC_SCOPE_FLAG,\r
391                      cx.hasCompileFunctionsWithDynamicScope());\r
392     }\r
393     \r
394     private void setSetupFlag(int flag, boolean value) {\r
395         setupFlags = (byte)(value ? setupFlags | flag : setupFlags & ~flag);\r
396     }\r
397 \r
398     /** \r
399      * Prepare this object to serve as the prototype property of constructor \r
400      * object with name <code>getClassName()<code> defined in\r
401      * <code>scope</code>.\r
402      * @param maxId maximum id available in prototype object\r
403      * @param cx current context\r
404      * @param scope object to define constructor in.\r
405      * @param sealed indicates whether object and all its properties should \r
406      *        be sealed \r
407      */ \r
408     public void addAsPrototype(int maxId, Context cx, Scriptable scope, \r
409                                boolean sealed) \r
410     {\r
411         activateIdMap(maxId);\r
412 \r
413         setSealFunctionsFlag(sealed);\r
414         setFunctionParametrs(cx);\r
415         \r
416         int constructorId = mapNameToId("constructor");\r
417         if (constructorId == 0) {\r
418             // It is a bug to call this function without id for constructor \r
419             throw new RuntimeException("No id for constructor property");\r
420         }\r
421 \r
422         IdFunction ctor = newIdFunction(constructorId);\r
423         ctor.initAsConstructor(scope, this);\r
424         fillConstructorProperties(cx, ctor, sealed);\r
425         if (sealed) {\r
426             ctor.sealObject();\r
427             ctor.addPropertyAttribute(READONLY);\r
428         }\r
429 \r
430         setParentScope(ctor);\r
431         setPrototype(getObjectPrototype(scope));\r
432         cacheIdValue(constructorId, ctor);\r
433 \r
434         if (sealed) {\r
435             sealObject();\r
436         }\r
437 \r
438         defineProperty(scope, getClassName(), ctor, ScriptableObject.DONTENUM);\r
439     }\r
440 \r
441     protected void fillConstructorProperties\r
442         (Context cx, IdFunction ctor, boolean sealed)\r
443     {\r
444     }\r
445 \r
446     protected void addIdFunctionProperty\r
447         (Scriptable obj, int id, boolean sealed)\r
448     {\r
449         IdFunction f = newIdFunction(id);\r
450         if (sealed) { f.sealObject(); }\r
451         defineProperty(obj, getIdName(id), f, DONTENUM);\r
452     }\r
453 \r
454     /** \r
455      * Utility method for converting target object into native this.\r
456      * Possible usage would be to have a private function like realThis:\r
457      * <pre>\r
458        private NativeSomething realThis(Scriptable thisObj,\r
459                                         IdFunction f, boolean readOnly)\r
460        {\r
461            while (!(thisObj instanceof NativeSomething)) {\r
462                thisObj = nextInstanceCheck(thisObj, f, readOnly);\r
463            }\r
464            return (NativeSomething)thisObj;\r
465        }\r
466     * </pre>\r
467     * Note that although such function can be implemented universally via\r
468     * java.lang.Class.isInstance(), it would be much more slower.\r
469     * @param readOnly specify if the function f does not change state of object.\r
470     * @return Scriptable object suitable for a check by the instanceof operator.\r
471     * @throws RuntimeException if no more instanceof target can be found\r
472     */\r
473     protected Scriptable nextInstanceCheck(Scriptable thisObj,\r
474                                            IdFunction f,\r
475                                            boolean readOnly)\r
476     {\r
477         if (readOnly && 0 != (setupFlags & USE_DYNAMIC_SCOPE_FLAG)) {\r
478             // for read only functions under dynamic scope look prototype chain\r
479             thisObj = thisObj.getPrototype();\r
480             if (thisObj != null) { return thisObj; }\r
481         }\r
482         throw NativeGlobal.typeError1("msg.incompat.call", \r
483                                       f.getFunctionName(), f);\r
484     }\r
485 \r
486     protected IdFunction newIdFunction(int id) {\r
487         IdFunction f = new IdFunction(this, getIdName(id), id);\r
488         if (0 != (setupFlags & SEAL_FUNCTIONS_FLAG)) { f.sealObject(); }\r
489         return f;\r
490     }\r
491 \r
492     protected final Object wrap_double(double x) {\r
493         return (x == x) ? new Double(x) : ScriptRuntime.NaNobj;\r
494     }\r
495 \r
496     protected final Object wrap_int(int x) {\r
497         byte b = (byte)x;\r
498         if (b == x) { return new Byte(b); }\r
499         return new Integer(x);\r
500     }\r
501 \r
502     protected final Object wrap_long(long x) {\r
503         int i = (int)x;\r
504         if (i == x) { return wrap_int(i); }\r
505         return new Long(x);\r
506     }\r
507 \r
508     protected final Object wrap_boolean(boolean x) {\r
509         return x ? Boolean.TRUE : Boolean.FALSE;\r
510     }\r
511     \r
512     private boolean hasValue(int id) {\r
513         Object value;\r
514         Object[] data = idMapData;\r
515         if (data == null || (value = data[id - 1]) == null) {\r
516             return hasIdValue(id);\r
517         }\r
518         else {\r
519             return value != NOT_FOUND;\r
520         }\r
521     }\r
522 \r
523     // Must be called only from synchronized (this)\r
524     private Object[] ensureIdData() {\r
525         Object[] data = idMapData;\r
526         if (data == null) { \r
527             idMapData = data = new Object[CACHE_NAMES ? maxId * 2 : maxId];\r
528         }\r
529         return data;\r
530     }\r
531     \r
532     private int getAttributes(int id) {\r
533         int attributes = getIdDefaultAttributes(id) | extraIdAttributes;\r
534         byte[] array = attributesArray;\r
535         if (array != null) {\r
536             attributes |= 0xFF & array[id - 1];\r
537         }\r
538         return attributes;\r
539     }\r
540 \r
541     private void setAttributes(int id, int attributes) {\r
542         int defaultAttrs = getIdDefaultAttributes(id);\r
543         if ((attributes & defaultAttrs) != defaultAttrs) {\r
544             // It is a bug to set attributes to less restrictive values \r
545             // then given by defaultAttrs\r
546             throw new RuntimeException("Attempt to unset default attributes");\r
547         }\r
548         // Store only additional bits\r
549         attributes &= ~defaultAttrs;\r
550         byte[] array = attributesArray;\r
551         if (array == null && attributes != 0) {\r
552             synchronized (this) {\r
553                 array = attributesArray;\r
554                 if (array == null) {\r
555                     attributesArray = array = new byte[maxId];\r
556                 }\r
557             }\r
558         }\r
559         if (array != null) {\r
560             array[id - 1] = (byte)attributes;\r
561         }\r
562     }\r
563 \r
564     private int maxId;\r
565     private Object[] idMapData;\r
566     private byte[] attributesArray;\r
567 \r
568     private static final boolean CACHE_NAMES = true;\r
569     private int lastIdCache;\r
570 \r
571     private static final int USE_DYNAMIC_SCOPE_FLAG = 1 << 0;\r
572     private static final int SEAL_FUNCTIONS_FLAG    = 1 << 1;\r
573     \r
574     private byte setupFlags;\r
575     private byte extraIdAttributes;\r
576 }\r
577 \r