2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / ScriptableObject.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; 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  * Norris Boyd\r
23  * Igor Bukanov\r
24  * Roger Lawrence\r
25  *\r
26  * Alternatively, the contents of this file may be used under the\r
27  * terms of the GNU Public License (the "GPL"), in which case the\r
28  * provisions of the GPL are applicable instead of those above.\r
29  * If you wish to allow use of your version of this file only\r
30  * under the terms of the GPL and not to allow others to use your\r
31  * version of this file under the NPL, indicate your decision by\r
32  * deleting the provisions above and replace them with the notice\r
33  * and other provisions required by the GPL.  If you do not delete\r
34  * the provisions above, a recipient may use your version of this\r
35  * file under either the NPL or the GPL.\r
36  */\r
37 \r
38 // API class\r
39 \r
40 package org.mozilla.javascript;\r
41 \r
42 import java.lang.reflect.*;\r
43 import java.util.Hashtable;\r
44 \r
45 /**\r
46  * This is the default implementation of the Scriptable interface. This\r
47  * class provides convenient default behavior that makes it easier to\r
48  * define host objects.\r
49  * <p>\r
50  * Various properties and methods of JavaScript objects can be conveniently\r
51  * defined using methods of ScriptableObject.\r
52  * <p>\r
53  * Classes extending ScriptableObject must define the getClassName method.\r
54  *\r
55  * @see org.mozilla.javascript.Scriptable\r
56  * @author Norris Boyd\r
57  */\r
58 \r
59 public abstract class ScriptableObject implements Scriptable {\r
60 \r
61     /**\r
62      * The empty property attribute.\r
63      *\r
64      * Used by getAttributes() and setAttributes().\r
65      *\r
66      * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
67      * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
68      */\r
69     public static final int EMPTY =     0x00;\r
70 \r
71     /**\r
72      * Property attribute indicating assignment to this property is ignored.\r
73      *\r
74      * @see org.mozilla.javascript.ScriptableObject#put\r
75      * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
76      * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
77      */\r
78     public static final int READONLY =  0x01;\r
79 \r
80     /**\r
81      * Property attribute indicating property is not enumerated.\r
82      *\r
83      * Only enumerated properties will be returned by getIds().\r
84      *\r
85      * @see org.mozilla.javascript.ScriptableObject#getIds\r
86      * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
87      * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
88      */\r
89     public static final int DONTENUM =  0x02;\r
90 \r
91     /**\r
92      * Property attribute indicating property cannot be deleted.\r
93      *\r
94      * @see org.mozilla.javascript.ScriptableObject#delete\r
95      * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
96      * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
97      */\r
98     public static final int PERMANENT = 0x04;\r
99 \r
100     /**\r
101      * Return the name of the class.\r
102      *\r
103      * This is typically the same name as the constructor.\r
104      * Classes extending ScriptableObject must implement this abstract\r
105      * method.\r
106      */\r
107     public abstract String getClassName();\r
108 \r
109     /**\r
110      * Returns true if the named property is defined.\r
111      *\r
112      * @param name the name of the property\r
113      * @param start the object in which the lookup began\r
114      * @return true if and only if the property was found in the object\r
115      */\r
116     public boolean has(String name, Scriptable start) {\r
117         return getSlot(name, name.hashCode(), false) != null;\r
118     }\r
119 \r
120     /**\r
121      * Returns true if the property index is defined.\r
122      *\r
123      * @param index the numeric index for the property\r
124      * @param start the object in which the lookup began\r
125      * @return true if and only if the property was found in the object\r
126      */\r
127     public boolean has(int index, Scriptable start) {\r
128         return getSlot(null, index, false) != null;\r
129     }\r
130 \r
131     /**\r
132      * Returns the value of the named property or NOT_FOUND.\r
133      *\r
134      * If the property was created using defineProperty, the\r
135      * appropriate getter method is called.\r
136      *\r
137      * @param name the name of the property\r
138      * @param start the object in which the lookup began\r
139      * @return the value of the property (may be null), or NOT_FOUND\r
140      */\r
141     public Object get(String name, Scriptable start) {\r
142         Slot slot = lastAccess; // Get local copy\r
143         if (name == slot.stringKey) {\r
144             if (slot.wasDeleted == 0) { return slot.value; }\r
145         } \r
146         int hashCode = name.hashCode();\r
147         slot = getSlot(name, hashCode, false);\r
148         if (slot == null)\r
149             return Scriptable.NOT_FOUND;\r
150         if ((slot.flags & Slot.HAS_GETTER) != 0) {\r
151             GetterSlot getterSlot = (GetterSlot) slot;\r
152             try {\r
153                 if (getterSlot.delegateTo == null) {\r
154                     // Walk the prototype chain to find an appropriate\r
155                     // object to invoke the getter on.\r
156                     Class clazz = getterSlot.getter.getDeclaringClass();\r
157                     while (!clazz.isInstance(start)) {\r
158                         start = start.getPrototype();\r
159                         if (start == null) {\r
160                             start = this;\r
161                             break;\r
162                         }\r
163                     }\r
164                     return getterSlot.getter.invoke(start, ScriptRuntime.emptyArgs);\r
165                 }\r
166                 Object[] args = { this };\r
167                 return getterSlot.getter.invoke(getterSlot.delegateTo, args);\r
168             }\r
169             catch (InvocationTargetException e) {\r
170                 throw WrappedException.wrapException(e);\r
171             }\r
172             catch (IllegalAccessException e) {\r
173                 throw WrappedException.wrapException(e);\r
174             }\r
175         }\r
176         // Here stringKey.equals(name) holds, but it can be that \r
177         // slot.stringKey != name. To make last name cache work, need\r
178         // to change the key\r
179         slot.stringKey = name;\r
180 \r
181         // Update cache. \r
182         lastAccess = slot;\r
183         return slot.value;\r
184     }\r
185 \r
186     /**\r
187      * Returns the value of the indexed property or NOT_FOUND.\r
188      *\r
189      * @param index the numeric index for the property\r
190      * @param start the object in which the lookup began\r
191      * @return the value of the property (may be null), or NOT_FOUND\r
192      */\r
193     public Object get(int index, Scriptable start) {\r
194         Slot slot = getSlot(null, index, false);\r
195         if (slot == null)\r
196             return Scriptable.NOT_FOUND;\r
197         return slot.value;\r
198     }\r
199     \r
200     /**\r
201      * Sets the value of the named property, creating it if need be.\r
202      *\r
203      * If the property was created using defineProperty, the\r
204      * appropriate setter method is called. <p>\r
205      *\r
206      * If the property's attributes include READONLY, no action is\r
207      * taken.\r
208      * This method will actually set the property in the start\r
209      * object.\r
210      *\r
211      * @param name the name of the property\r
212      * @param start the object whose property is being set\r
213      * @param value value to set the property to\r
214      */\r
215     public void put(String name, Scriptable start, Object value) {\r
216         int hash = name.hashCode();\r
217         Slot slot = getSlot(name, hash, false);\r
218         if (slot == null) {\r
219             if (start != this) {\r
220                 start.put(name, start, value);\r
221                 return;\r
222             }\r
223             slot = getSlotToSet(name, hash, false);\r
224         }\r
225         if ((slot.attributes & ScriptableObject.READONLY) != 0)\r
226             return;\r
227         if ((slot.flags & Slot.HAS_SETTER) != 0) {\r
228             GetterSlot getterSlot = (GetterSlot) slot;\r
229             try {\r
230                 Class pTypes[] = getterSlot.setter.getParameterTypes();\r
231                 Class desired = pTypes[pTypes.length - 1];\r
232                 Object actualArg\r
233                         = FunctionObject.convertArg(start, value, desired);\r
234                 if (getterSlot.delegateTo == null) {\r
235                     // Walk the prototype chain to find an appropriate\r
236                     // object to invoke the setter on.\r
237                     Object[] arg = { actualArg };\r
238                     Class clazz = getterSlot.setter.getDeclaringClass();\r
239                     while (!clazz.isInstance(start)) {\r
240                         start = start.getPrototype();\r
241                         if (start == null) {\r
242                             start = this;\r
243                             break;\r
244                         }\r
245                     }\r
246                     Object v = getterSlot.setter.invoke(start, arg);\r
247                     if (getterSlot.setterReturnsValue) {\r
248                         slot.value = v;\r
249                         if (!(v instanceof Method))\r
250                             slot.flags = 0;\r
251                     }\r
252                     return;\r
253                 }\r
254                 Object[] args = { this, actualArg };\r
255                 Object v = getterSlot.setter.invoke(getterSlot.delegateTo, args);\r
256                 if (getterSlot.setterReturnsValue) {\r
257                     slot.value = v;\r
258                     if (!(v instanceof Method))\r
259                         slot.flags = 0;\r
260                 }\r
261                 return;\r
262             }\r
263             catch (InvocationTargetException e) {\r
264                 throw WrappedException.wrapException(e);\r
265             }\r
266             catch (IllegalAccessException e) {\r
267                 throw WrappedException.wrapException(e);\r
268             }\r
269         }\r
270         if (this == start) {\r
271             slot.value = value;\r
272             // Make cache work\r
273             slot.stringKey = name;\r
274             lastAccess = slot;\r
275         } else {\r
276             start.put(name, start, value);\r
277         }\r
278     }\r
279 \r
280     /**\r
281      * Sets the value of the indexed property, creating it if need be.\r
282      *\r
283      * @param index the numeric index for the property\r
284      * @param start the object whose property is being set\r
285      * @param value value to set the property to\r
286      */\r
287     public void put(int index, Scriptable start, Object value) {\r
288         Slot slot = getSlot(null, index, false);\r
289         if (slot == null) {\r
290             if (start != this) {\r
291                 start.put(index, start, value);\r
292                 return;\r
293             }\r
294             slot = getSlotToSet(null, index, false);\r
295         }\r
296         if ((slot.attributes & ScriptableObject.READONLY) != 0)\r
297             return;\r
298         if (this == start) {\r
299             slot.value = value;\r
300         } else {\r
301             start.put(index, start, value);\r
302         }\r
303     }\r
304 \r
305     /**\r
306      * Removes a named property from the object.\r
307      *\r
308      * If the property is not found, or it has the PERMANENT attribute,\r
309      * no action is taken.\r
310      *\r
311      * @param name the name of the property\r
312      */\r
313     public void delete(String name) {\r
314         removeSlot(name, name.hashCode());\r
315     }\r
316 \r
317     /**\r
318      * Removes the indexed property from the object.\r
319      *\r
320      * If the property is not found, or it has the PERMANENT attribute,\r
321      * no action is taken.\r
322      *\r
323      * @param index the numeric index for the property\r
324      */\r
325     public void delete(int index) {\r
326         removeSlot(null, index);\r
327     }\r
328 \r
329     /**\r
330      * Get the attributes of a named property.\r
331      *\r
332      * The property is specified by <code>name</code>\r
333      * as defined for <code>has</code>.<p>\r
334      *\r
335      * @param name the identifier for the property\r
336      * @param start the object in which the lookup began\r
337      * @return the bitset of attributes\r
338      * @exception PropertyException if the named property\r
339      *            is not found\r
340      * @see org.mozilla.javascript.ScriptableObject#has\r
341      * @see org.mozilla.javascript.ScriptableObject#READONLY\r
342      * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
343      * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
344      * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
345      */\r
346     public int getAttributes(String name, Scriptable start)\r
347         throws PropertyException\r
348     {\r
349         Slot slot = getSlot(name, name.hashCode(), false);\r
350         if (slot == null) {\r
351             throw PropertyException.withMessage0("msg.prop.not.found");\r
352         }\r
353         return slot.attributes;\r
354     }\r
355 \r
356     /**\r
357      * Get the attributes of an indexed property.\r
358      *\r
359      * @param index the numeric index for the property\r
360      * @param start the object in which the lookup began\r
361      * @exception PropertyException if the indexed property\r
362      *            is not found\r
363      * @return the bitset of attributes\r
364      * @see org.mozilla.javascript.ScriptableObject#has\r
365      * @see org.mozilla.javascript.ScriptableObject#READONLY\r
366      * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
367      * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
368      * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
369      */\r
370     public int getAttributes(int index, Scriptable start)\r
371         throws PropertyException\r
372     {\r
373         Slot slot = getSlot(null, index, false);\r
374         if (slot == null) {\r
375             throw PropertyException.withMessage0("msg.prop.not.found");\r
376         }\r
377         return slot.attributes;\r
378     }\r
379 \r
380     /**\r
381      * Set the attributes of a named property.\r
382      *\r
383      * The property is specified by <code>name</code>\r
384      * as defined for <code>has</code>.<p>\r
385      *\r
386      * The possible attributes are READONLY, DONTENUM,\r
387      * and PERMANENT. Combinations of attributes\r
388      * are expressed by the bitwise OR of attributes.\r
389      * EMPTY is the state of no attributes set. Any unused\r
390      * bits are reserved for future use.\r
391      *\r
392      * @param name the name of the property\r
393      * @param start the object in which the lookup began\r
394      * @param attributes the bitset of attributes\r
395      * @exception PropertyException if the named property\r
396      *            is not found\r
397      * @see org.mozilla.javascript.Scriptable#has\r
398      * @see org.mozilla.javascript.ScriptableObject#READONLY\r
399      * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
400      * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
401      * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
402      */\r
403     public void setAttributes(String name, Scriptable start,\r
404                               int attributes)\r
405         throws PropertyException\r
406     {\r
407         final int mask = READONLY | DONTENUM | PERMANENT;\r
408         attributes &= mask; // mask out unused bits\r
409         Slot slot = getSlot(name, name.hashCode(), false);\r
410         if (slot == null) {\r
411             throw PropertyException.withMessage0("msg.prop.not.found");\r
412         }\r
413         slot.attributes = (short) attributes;\r
414     }\r
415 \r
416     /**\r
417      * Set the attributes of an indexed property.\r
418      *\r
419      * @param index the numeric index for the property\r
420      * @param start the object in which the lookup began\r
421      * @param attributes the bitset of attributes\r
422      * @exception PropertyException if the indexed property\r
423      *            is not found\r
424      * @see org.mozilla.javascript.Scriptable#has\r
425      * @see org.mozilla.javascript.ScriptableObject#READONLY\r
426      * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
427      * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
428      * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
429      */\r
430     public void setAttributes(int index, Scriptable start,\r
431                               int attributes)\r
432         throws PropertyException\r
433     {\r
434         Slot slot = getSlot(null, index, false);\r
435         if (slot == null) {\r
436             throw PropertyException.withMessage0("msg.prop.not.found");\r
437         }\r
438         slot.attributes = (short) attributes;\r
439     }\r
440 \r
441     /**\r
442      * Returns the prototype of the object.\r
443      */\r
444     public Scriptable getPrototype() {\r
445         return prototype;\r
446     }\r
447 \r
448     /**\r
449      * Sets the prototype of the object.\r
450      */\r
451     public void setPrototype(Scriptable m) {\r
452         prototype = m;\r
453     }\r
454 \r
455     /**\r
456      * Returns the parent (enclosing) scope of the object.\r
457      */\r
458     public Scriptable getParentScope() {\r
459         return parent;\r
460     }\r
461 \r
462     /**\r
463      * Sets the parent (enclosing) scope of the object.\r
464      */\r
465     public void setParentScope(Scriptable m) {\r
466         parent = m;\r
467     }\r
468 \r
469     /**\r
470      * Returns an array of ids for the properties of the object.\r
471      *\r
472      * <p>Any properties with the attribute DONTENUM are not listed. <p>\r
473      *\r
474      * @return an array of java.lang.Objects with an entry for every\r
475      * listed property. Properties accessed via an integer index will \r
476      * have a corresponding\r
477      * Integer entry in the returned array. Properties accessed by\r
478      * a String will have a String entry in the returned array.\r
479      */\r
480     public Object[] getIds() {\r
481         return getIds(false);\r
482     }\r
483     \r
484     /**\r
485      * Returns an array of ids for the properties of the object.\r
486      *\r
487      * <p>All properties, even those with attribute DONTENUM, are listed. <p>\r
488      *\r
489      * @return an array of java.lang.Objects with an entry for every\r
490      * listed property. Properties accessed via an integer index will \r
491      * have a corresponding\r
492      * Integer entry in the returned array. Properties accessed by\r
493      * a String will have a String entry in the returned array.\r
494      */\r
495     public Object[] getAllIds() {\r
496         return getIds(true);\r
497     }\r
498     \r
499     /**\r
500      * Implements the [[DefaultValue]] internal method.\r
501      *\r
502      * <p>Note that the toPrimitive conversion is a no-op for\r
503      * every type other than Object, for which [[DefaultValue]]\r
504      * is called. See ECMA 9.1.<p>\r
505      *\r
506      * A <code>hint</code> of null means "no hint".\r
507      *\r
508      * @param typeHint the type hint\r
509      * @return the default value for the object\r
510      *\r
511      * See ECMA 8.6.2.6.\r
512      */\r
513     public Object getDefaultValue(Class typeHint) {\r
514         Object val;\r
515         Context cx = null;\r
516         try {\r
517             for (int i=0; i < 2; i++) {\r
518                 if (typeHint == ScriptRuntime.StringClass ? i == 0 : i == 1) {\r
519                     Object v = getProperty(this, "toString");\r
520                     if (!(v instanceof Function))\r
521                         continue;\r
522                     Function fun = (Function) v;\r
523                     if (cx == null)\r
524                         cx = Context.getContext();\r
525                     val = fun.call(cx, fun.getParentScope(), this,\r
526                                    ScriptRuntime.emptyArgs);\r
527                 } else {\r
528                     String hint;\r
529                     if (typeHint == null)\r
530                         hint = "undefined";\r
531                     else if (typeHint == ScriptRuntime.StringClass)\r
532                         hint = "string";\r
533                     else if (typeHint == ScriptRuntime.ScriptableClass)\r
534                         hint = "object";\r
535                     else if (typeHint == ScriptRuntime.FunctionClass)\r
536                         hint = "function";\r
537                     else if (typeHint == ScriptRuntime.BooleanClass || \r
538                                                 typeHint == Boolean.TYPE)\r
539                         hint = "boolean";\r
540                     else if (typeHint == ScriptRuntime.NumberClass ||\r
541                              typeHint == ScriptRuntime.ByteClass || \r
542                              typeHint == Byte.TYPE ||\r
543                              typeHint == ScriptRuntime.ShortClass || \r
544                              typeHint == Short.TYPE ||\r
545                              typeHint == ScriptRuntime.IntegerClass || \r
546                              typeHint == Integer.TYPE ||\r
547                              typeHint == ScriptRuntime.FloatClass || \r
548                              typeHint == Float.TYPE ||\r
549                              typeHint == ScriptRuntime.DoubleClass || \r
550                              typeHint == Double.TYPE)\r
551                         hint = "number";\r
552                     else {\r
553                         throw Context.reportRuntimeError1(\r
554                             "msg.invalid.type", typeHint.toString());\r
555                     }\r
556                     Object v = getProperty(this, "valueOf");\r
557                     if (!(v instanceof Function))\r
558                         continue;\r
559                     Function fun = (Function) v;\r
560                     Object[] args = { hint };\r
561                     if (cx == null)\r
562                         cx = Context.getContext();\r
563                     val = fun.call(cx, fun.getParentScope(), this, args);\r
564                 }\r
565                 if (val != null && (val == Undefined.instance ||\r
566                                     !(val instanceof Scriptable) ||\r
567                                     typeHint == Scriptable.class ||\r
568                                     typeHint == Function.class))\r
569                 {\r
570                     return val;\r
571                 }\r
572                 if (val instanceof NativeJavaObject) {\r
573                     // Let a wrapped java.lang.String pass for a primitive \r
574                     // string.\r
575                     Object u = ((Wrapper) val).unwrap();\r
576                     if (u instanceof String)\r
577                         return u;\r
578                 }\r
579             }\r
580             // fall through to error \r
581         }\r
582         catch (JavaScriptException jse) {\r
583             // fall through to error \r
584         }\r
585         Object arg = (typeHint == null) ? "undefined" : typeHint.toString();\r
586         throw NativeGlobal.typeError1("msg.default.value", arg, this);\r
587     }\r
588 \r
589     /**\r
590      * Implements the instanceof operator.\r
591      *\r
592      * <p>This operator has been proposed to ECMA.\r
593      *\r
594      * @param instance The value that appeared on the LHS of the instanceof\r
595      *              operator\r
596      * @return true if "this" appears in value's prototype chain\r
597      *\r
598      */\r
599     public boolean hasInstance(Scriptable instance) {\r
600         // Default for JS objects (other than Function) is to do prototype\r
601         // chasing.  This will be overridden in NativeFunction and non-JS objects.\r
602 \r
603         return ScriptRuntime.jsDelegatesTo(instance, this);\r
604     }\r
605 \r
606     /**\r
607      * Defines JavaScript objects from a Java class that implements Scriptable.\r
608      *\r
609      * If the given class has a method\r
610      * <pre>\r
611      * static void init(Context cx, Scriptable scope, boolean sealed);</pre>\r
612      *\r
613      * or its compatibility form \r
614      * <pre>\r
615      * static void init(Scriptable scope);</pre>\r
616      *\r
617      * then it is invoked and no further initialization is done.<p>\r
618      *\r
619      * However, if no such a method is found, then the class's constructors and\r
620      * methods are used to initialize a class in the following manner.<p>\r
621      *\r
622      * First, the zero-parameter constructor of the class is called to\r
623      * create the prototype. If no such constructor exists,\r
624      * a ClassDefinitionException is thrown. <p>\r
625      *\r
626      * Next, all methods are scanned for special prefixes that indicate that they\r
627      * have special meaning for defining JavaScript objects.\r
628      * These special prefixes are\r
629      * <ul>\r
630      * <li><code>jsFunction_</code> for a JavaScript function\r
631      * <li><code>jsStaticFunction_</code> for a JavaScript function that \r
632      *           is a property of the constructor\r
633      * <li><code>jsGet_</code> for a getter of a JavaScript property\r
634      * <li><code>jsSet_</code> for a setter of a JavaScript property\r
635      * <li><code>jsConstructor</code> for a JavaScript function that \r
636      *           is the constructor\r
637      * </ul><p>\r
638      *\r
639      * If the method's name begins with "jsFunction_", a JavaScript function \r
640      * is created with a name formed from the rest of the Java method name \r
641      * following "jsFunction_". So a Java method named "jsFunction_foo" will\r
642      * define a JavaScript method "foo". Calling this JavaScript function \r
643      * will cause the Java method to be called. The parameters of the method\r
644      * must be of number and types as defined by the FunctionObject class.\r
645      * The JavaScript function is then added as a property\r
646      * of the prototype. <p>\r
647      * \r
648      * If the method's name begins with "jsStaticFunction_", it is handled\r
649      * similarly except that the resulting JavaScript function is added as a \r
650      * property of the constructor object. The Java method must be static.\r
651      * \r
652      * If the method's name begins with "jsGet_" or "jsSet_", the method is\r
653      * considered to define a property. Accesses to the defined property\r
654      * will result in calls to these getter and setter methods. If no\r
655      * setter is defined, the property is defined as READONLY.<p>\r
656      *\r
657      * If the method's name is "jsConstructor", the method is\r
658      * considered to define the body of the constructor. Only one \r
659      * method of this name may be defined. \r
660      * If no method is found that can serve as constructor, a Java\r
661      * constructor will be selected to serve as the JavaScript\r
662      * constructor in the following manner. If the class has only one\r
663      * Java constructor, that constructor is used to define\r
664      * the JavaScript constructor. If the the class has two constructors,\r
665      * one must be the zero-argument constructor (otherwise an\r
666      * ClassDefinitionException would have already been thrown\r
667      * when the prototype was to be created). In this case\r
668      * the Java constructor with one or more parameters will be used\r
669      * to define the JavaScript constructor. If the class has three\r
670      * or more constructors, an ClassDefinitionException\r
671      * will be thrown.<p>\r
672      *\r
673      * Finally, if there is a method\r
674      * <pre>\r
675      * static void finishInit(Scriptable scope, FunctionObject constructor,\r
676      *                        Scriptable prototype)</pre>\r
677      *\r
678      * it will be called to finish any initialization. The <code>scope</code>\r
679      * argument will be passed, along with the newly created constructor and\r
680      * the newly created prototype.<p>\r
681      *\r
682      * @param scope The scope in which to define the constructor\r
683      * @param clazz The Java class to use to define the JavaScript objects\r
684      *              and properties\r
685      * @exception IllegalAccessException if access is not available\r
686      *            to a reflected class member\r
687      * @exception InstantiationException if unable to instantiate\r
688      *            the named class\r
689      * @exception InvocationTargetException if an exception is thrown\r
690      *            during execution of methods of the named class\r
691      * @exception ClassDefinitionException if an appropriate\r
692      *            constructor cannot be found to create the prototype\r
693      * @exception PropertyException if getter and setter\r
694      *            methods do not conform to the requirements of the\r
695      *            defineProperty method\r
696      * @see org.mozilla.javascript.Function\r
697      * @see org.mozilla.javascript.FunctionObject\r
698      * @see org.mozilla.javascript.ScriptableObject#READONLY\r
699      * @see org.mozilla.javascript.ScriptableObject#defineProperty\r
700      */\r
701     public static void defineClass(Scriptable scope, Class clazz)\r
702         throws IllegalAccessException, InstantiationException,\r
703                InvocationTargetException, ClassDefinitionException,\r
704                PropertyException\r
705     {\r
706         defineClass(scope, clazz, false);\r
707     }\r
708     \r
709     /**\r
710      * Defines JavaScript objects from a Java class, optionally \r
711      * allowing sealing.\r
712      *\r
713      * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>\r
714      * except that sealing is allowed. An object that is sealed cannot have \r
715      * properties added or removed. Note that sealing is not allowed in\r
716      * the current ECMA/ISO language specification, but is likely for\r
717      * the next version.\r
718      * \r
719      * @param scope The scope in which to define the constructor\r
720      * @param clazz The Java class to use to define the JavaScript objects\r
721      *              and properties. The class must implement Scriptable.\r
722      * @param sealed whether or not to create sealed standard objects that\r
723      *               cannot be modified. \r
724      * @exception IllegalAccessException if access is not available\r
725      *            to a reflected class member\r
726      * @exception InstantiationException if unable to instantiate\r
727      *            the named class\r
728      * @exception InvocationTargetException if an exception is thrown\r
729      *            during execution of methods of the named class\r
730      * @exception ClassDefinitionException if an appropriate\r
731      *            constructor cannot be found to create the prototype\r
732      * @exception PropertyException if getter and setter\r
733      *            methods do not conform to the requirements of the\r
734      *            defineProperty method\r
735      * @since 1.4R3\r
736      */\r
737     public static void defineClass(Scriptable scope, Class clazz, \r
738                                    boolean sealed)\r
739         throws IllegalAccessException, InstantiationException,\r
740                InvocationTargetException, ClassDefinitionException,\r
741                PropertyException\r
742     {\r
743         Method[] methods = FunctionObject.getMethodList(clazz);\r
744         for (int i=0; i < methods.length; i++) {\r
745             Method method = methods[i];\r
746             if (!method.getName().equals("init"))\r
747                 continue;\r
748             Class[] parmTypes = method.getParameterTypes();\r
749             if (parmTypes.length == 3 &&\r
750                 parmTypes[0] == ContextClass &&\r
751                 parmTypes[1] == ScriptRuntime.ScriptableClass &&\r
752                 parmTypes[2] == Boolean.TYPE &&\r
753                 Modifier.isStatic(method.getModifiers()))\r
754             {\r
755                 Object args[] = { Context.getContext(), scope, \r
756                                   sealed ? Boolean.TRUE : Boolean.FALSE };\r
757                 method.invoke(null, args);\r
758                 return;\r
759             }\r
760             if (parmTypes.length == 1 &&\r
761                 parmTypes[0] == ScriptRuntime.ScriptableClass &&\r
762                 Modifier.isStatic(method.getModifiers()))\r
763             {\r
764                 Object args[] = { scope };\r
765                 method.invoke(null, args);\r
766                 return;\r
767             }\r
768             \r
769         }\r
770 \r
771         // If we got here, there isn't an "init" method with the right\r
772         // parameter types.\r
773         Hashtable exclusionList = getExclusionList();\r
774 \r
775         Constructor[] ctors = clazz.getConstructors();\r
776         Constructor protoCtor = null;\r
777         for (int i=0; i < ctors.length; i++) {\r
778             if (ctors[i].getParameterTypes().length == 0) {\r
779                 protoCtor = ctors[i];\r
780                 break;\r
781             }\r
782         }\r
783         if (protoCtor == null) {\r
784             throw new ClassDefinitionException(\r
785                     Context.getMessage1("msg.zero.arg.ctor", clazz.getName()));\r
786         }\r
787 \r
788         Scriptable proto = (Scriptable) \r
789                         protoCtor.newInstance(ScriptRuntime.emptyArgs);\r
790         proto.setPrototype(getObjectPrototype(scope));\r
791         String className = proto.getClassName();\r
792 \r
793         // Find out whether there are any methods that begin with\r
794         // "js". If so, then only methods that begin with special\r
795         // prefixes will be defined as JavaScript entities.\r
796         // The prefixes "js_" and "jsProperty_" are deprecated.\r
797         final String genericPrefix = "js_";\r
798         final String functionPrefix = "jsFunction_";\r
799         final String staticFunctionPrefix = "jsStaticFunction_";\r
800         final String propertyPrefix = "jsProperty_";\r
801         final String getterPrefix = "jsGet_";\r
802         final String setterPrefix = "jsSet_";\r
803         final String ctorName = "jsConstructor";\r
804 \r
805         boolean hasPrefix = false;\r
806         Method[] ctorMeths = FunctionObject.findMethods(clazz, ctorName);\r
807         Member ctorMember = null;\r
808         if (ctorMeths != null) {\r
809             if (ctorMeths.length > 1) {\r
810                 throw new ClassDefinitionException(\r
811                     Context.getMessage2("msg.multiple.ctors", \r
812                                         ctorMeths[0], ctorMeths[1]));\r
813             }\r
814             ctorMember = ctorMeths[0];\r
815             hasPrefix = true;\r
816         }\r
817 \r
818         // Deprecated: look for functions with the same name as the class\r
819         // and consider them constructors.\r
820         for (int i=0; i < methods.length; i++) {\r
821             String name = methods[i].getName();\r
822             String prefix = null;\r
823             if (!name.startsWith("js")) // common start to all prefixes\r
824                 prefix = null;\r
825             else if (name.startsWith(genericPrefix))\r
826                 prefix = genericPrefix;\r
827             else if (name.startsWith(functionPrefix))\r
828                 prefix = functionPrefix;\r
829             else if (name.startsWith(staticFunctionPrefix))\r
830                 prefix = staticFunctionPrefix;\r
831             else if (name.startsWith(propertyPrefix))\r
832                 prefix = propertyPrefix;\r
833             else if (name.startsWith(getterPrefix))\r
834                 prefix = getterPrefix;\r
835             else if (name.startsWith(setterPrefix))\r
836                 prefix = setterPrefix;\r
837             if (prefix != null) {\r
838                 hasPrefix = true;\r
839                 name = name.substring(prefix.length());\r
840             }\r
841             if (name.equals(className)) {\r
842                 if (ctorMember != null) {\r
843                     throw new ClassDefinitionException(\r
844                         Context.getMessage2("msg.multiple.ctors", \r
845                                             ctorMember, methods[i]));\r
846                 }\r
847                 ctorMember = methods[i];\r
848             }\r
849         }\r
850 \r
851         if (ctorMember == null) {\r
852             if (ctors.length == 1) {\r
853                 ctorMember = ctors[0];\r
854             } else if (ctors.length == 2) {\r
855                 if (ctors[0].getParameterTypes().length == 0)\r
856                     ctorMember = ctors[1];\r
857                 else if (ctors[1].getParameterTypes().length == 0)\r
858                     ctorMember = ctors[0];\r
859             }\r
860             if (ctorMember == null) {\r
861                 throw new ClassDefinitionException(\r
862                     Context.getMessage1("msg.ctor.multiple.parms",\r
863                                         clazz.getName()));\r
864             }\r
865         }\r
866 \r
867         FunctionObject ctor = new FunctionObject(className, ctorMember, scope);\r
868         if (ctor.isVarArgsMethod()) {\r
869             throw Context.reportRuntimeError1\r
870                 ("msg.varargs.ctor", ctorMember.getName());\r
871         }\r
872         ctor.addAsConstructor(scope, proto);\r
873 \r
874         if (!hasPrefix && exclusionList == null)\r
875             exclusionList = getExclusionList();\r
876         Method finishInit = null;\r
877         for (int i=0; i < methods.length; i++) {\r
878             if (!hasPrefix && methods[i].getDeclaringClass() != clazz)\r
879                 continue;\r
880             String name = methods[i].getName();\r
881             if (name.equals("finishInit")) {\r
882                 Class[] parmTypes = methods[i].getParameterTypes();\r
883                 if (parmTypes.length == 3 &&\r
884                     parmTypes[0] == ScriptRuntime.ScriptableClass &&\r
885                     parmTypes[1] == FunctionObject.class &&\r
886                     parmTypes[2] == ScriptRuntime.ScriptableClass &&\r
887                     Modifier.isStatic(methods[i].getModifiers()))\r
888                 {\r
889                     finishInit = methods[i];\r
890                     continue;\r
891                 }\r
892             }\r
893             // ignore any compiler generated methods.\r
894             if (name.indexOf('$') != -1)\r
895                 continue;\r
896             if (name.equals(ctorName))\r
897                 continue;\r
898             String prefix = null;\r
899             if (hasPrefix) {\r
900                 if (name.startsWith(genericPrefix)) {\r
901                     prefix = genericPrefix;\r
902                 } else if (name.startsWith(functionPrefix)) {\r
903                     prefix = functionPrefix;\r
904                 } else if (name.startsWith(staticFunctionPrefix)) {\r
905                     prefix = staticFunctionPrefix;\r
906                     if (!Modifier.isStatic(methods[i].getModifiers())) {\r
907                         throw new ClassDefinitionException(\r
908                             "jsStaticFunction must be used with static method.");\r
909                     }\r
910                 } else if (name.startsWith(propertyPrefix)) {\r
911                     prefix = propertyPrefix;\r
912                 } else if (name.startsWith(getterPrefix)) {\r
913                     prefix = getterPrefix;\r
914                 } else if (name.startsWith(setterPrefix)) {\r
915                     prefix = setterPrefix;\r
916                 } else {\r
917                     continue;\r
918                 }\r
919                 name = name.substring(prefix.length());\r
920             } else if (exclusionList.get(name) != null)\r
921                 continue;\r
922             if (methods[i] == ctorMember) {\r
923                 continue;\r
924             }\r
925             if (prefix != null && prefix.equals(setterPrefix))\r
926                 continue;   // deal with set when we see get\r
927             if (prefix != null && prefix.equals(getterPrefix)) {\r
928                 if (!(proto instanceof ScriptableObject)) {\r
929                     throw PropertyException.withMessage2\r
930                         ("msg.extend.scriptable",                                                         proto.getClass().toString(), name);\r
931                 }\r
932                 Method[] setter = FunctionObject.findMethods(\r
933                                     clazz,\r
934                                     setterPrefix + name);\r
935                 if (setter != null && setter.length != 1) {\r
936                     throw PropertyException.withMessage2\r
937                         ("msg.no.overload", name, clazz.getName());\r
938                 }\r
939                 int attr = ScriptableObject.PERMANENT |\r
940                            ScriptableObject.DONTENUM  |\r
941                            (setter != null ? 0\r
942                                            : ScriptableObject.READONLY);\r
943                 Method m = setter == null ? null : setter[0];\r
944                 ((ScriptableObject) proto).defineProperty(name, null,\r
945                                                           methods[i], m,\r
946                                                           attr);\r
947                 continue;\r
948             }\r
949             if ((name.startsWith("get") || name.startsWith("set")) &&\r
950                 name.length() > 3 &&\r
951                 !(hasPrefix && (prefix.equals(functionPrefix) ||\r
952                                 prefix.equals(staticFunctionPrefix))))\r
953             {\r
954                 if (!(proto instanceof ScriptableObject)) {\r
955                     throw PropertyException.withMessage2\r
956                         ("msg.extend.scriptable",\r
957                          proto.getClass().toString(), name);\r
958                 }\r
959                 if (name.startsWith("set"))\r
960                     continue;   // deal with set when we see get\r
961                 StringBuffer buf = new StringBuffer();\r
962                 char c = name.charAt(3);\r
963                 buf.append(Character.toLowerCase(c));\r
964                 if (name.length() > 4)\r
965                     buf.append(name.substring(4));\r
966                 String propertyName = buf.toString();\r
967                 buf.setCharAt(0, c);\r
968                 buf.insert(0, "set");\r
969                 String setterName = buf.toString();\r
970                 Method[] setter = FunctionObject.findMethods(\r
971                                     clazz,\r
972                                     hasPrefix ? genericPrefix + setterName\r
973                                               : setterName);\r
974                 if (setter != null && setter.length != 1) {\r
975                     throw PropertyException.withMessage2\r
976                         ("msg.no.overload", name, clazz.getName());\r
977                 }\r
978                 if (setter == null && hasPrefix)\r
979                     setter = FunctionObject.findMethods(\r
980                                 clazz,\r
981                                 propertyPrefix + setterName);\r
982                 int attr = ScriptableObject.PERMANENT |\r
983                            ScriptableObject.DONTENUM  |\r
984                            (setter != null ? 0\r
985                                            : ScriptableObject.READONLY);\r
986                 Method m = setter == null ? null : setter[0];\r
987                 ((ScriptableObject) proto).defineProperty(propertyName, null,\r
988                                                           methods[i], m,\r
989                                                           attr);\r
990                 continue;\r
991             }\r
992             FunctionObject f = new FunctionObject(name, methods[i], proto);\r
993             if (f.isVarArgsConstructor()) {\r
994                 throw Context.reportRuntimeError1\r
995                     ("msg.varargs.fun", ctorMember.getName());\r
996             }\r
997             Scriptable dest = prefix == staticFunctionPrefix\r
998                               ? ctor\r
999                               : proto;\r
1000             defineProperty(dest, name, f, DONTENUM);\r
1001             if (sealed) {\r
1002                 f.sealObject();\r
1003                 f.addPropertyAttribute(READONLY);\r
1004             }\r
1005         }\r
1006 \r
1007         if (finishInit != null) {\r
1008             // call user code to complete the initialization\r
1009             Object[] finishArgs = { scope, ctor, proto };\r
1010             finishInit.invoke(null, finishArgs);\r
1011         }\r
1012         \r
1013         if (sealed) {\r
1014             ctor.sealObject();\r
1015             ctor.addPropertyAttribute(READONLY);\r
1016             if (proto instanceof ScriptableObject) {\r
1017                 ((ScriptableObject) proto).sealObject();\r
1018                 ((ScriptableObject) proto).addPropertyAttribute(READONLY);\r
1019             }\r
1020         }\r
1021     }\r
1022 \r
1023     /**\r
1024      * Define a JavaScript property.\r
1025      *\r
1026      * Creates the property with an initial value and sets its attributes.\r
1027      *\r
1028      * @param propertyName the name of the property to define.\r
1029      * @param value the initial value of the property\r
1030      * @param attributes the attributes of the JavaScript property\r
1031      * @see org.mozilla.javascript.Scriptable#put\r
1032      */\r
1033     public void defineProperty(String propertyName, Object value,\r
1034                                int attributes)\r
1035     {\r
1036         put(propertyName, this, value);\r
1037         try {\r
1038             setAttributes(propertyName, this, attributes);\r
1039         }\r
1040         catch (PropertyException e) {\r
1041             throw new RuntimeException("Cannot create property");\r
1042         }\r
1043     }\r
1044 \r
1045     /**\r
1046      * Utility method to add properties to arbitrary Scriptable object.\r
1047      * If destination is instance of ScriptableObject, calls \r
1048      * defineProperty there, otherwise calls put in destination \r
1049      * ignoring attributes\r
1050      */\r
1051     public static void defineProperty(Scriptable destination, \r
1052                                       String propertyName, Object value,\r
1053                                       int attributes)\r
1054     {\r
1055         if (destination instanceof ScriptableObject) {\r
1056             ScriptableObject obj = (ScriptableObject)destination;\r
1057             obj.defineProperty(propertyName, value, attributes);\r
1058         }\r
1059         else {\r
1060             destination.put(propertyName, destination, value);\r
1061         }\r
1062     }\r
1063  \r
1064     /**\r
1065      * Define a JavaScript property with getter and setter side effects.\r
1066      *\r
1067      * If the setter is not found, the attribute READONLY is added to\r
1068      * the given attributes. <p>\r
1069      *\r
1070      * The getter must be a method with zero parameters, and the setter, if\r
1071      * found, must be a method with one parameter.<p>\r
1072      *\r
1073      * @param propertyName the name of the property to define. This name\r
1074      *                    also affects the name of the setter and getter\r
1075      *                    to search for. If the propertyId is "foo", then\r
1076      *                    <code>clazz</code> will be searched for "getFoo"\r
1077      *                    and "setFoo" methods.\r
1078      * @param clazz the Java class to search for the getter and setter\r
1079      * @param attributes the attributes of the JavaScript property\r
1080      * @exception PropertyException if multiple methods\r
1081      *            are found for the getter or setter, or if the getter\r
1082      *            or setter do not conform to the forms described in\r
1083      *            <code>defineProperty(String, Object, Method, Method,\r
1084      *            int)</code>\r
1085      * @see org.mozilla.javascript.Scriptable#put\r
1086      */\r
1087     public void defineProperty(String propertyName, Class clazz,\r
1088                                int attributes)\r
1089         throws PropertyException\r
1090     {\r
1091         StringBuffer buf = new StringBuffer(propertyName);\r
1092         buf.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));\r
1093         String s = buf.toString();\r
1094         Method[] getter = FunctionObject.findMethods(clazz, "get" + s);\r
1095         Method[] setter = FunctionObject.findMethods(clazz, "set" + s);\r
1096         if (setter == null)\r
1097             attributes |= ScriptableObject.READONLY;\r
1098         if (getter.length != 1 || (setter != null && setter.length != 1)) {\r
1099             throw PropertyException.withMessage2\r
1100                 ("msg.no.overload", propertyName, clazz.getName());\r
1101         }\r
1102         defineProperty(propertyName, null, getter[0],\r
1103                        setter == null ? null : setter[0], attributes);\r
1104     }\r
1105 \r
1106     /**\r
1107      * Define a JavaScript property.\r
1108      *\r
1109      * Use this method only if you wish to define getters and setters for\r
1110      * a given property in a ScriptableObject. To create a property without\r
1111      * special getter or setter side effects, use\r
1112      * <code>defineProperty(String,int)</code>.\r
1113      *\r
1114      * If <code>setter</code> is null, the attribute READONLY is added to\r
1115      * the given attributes.<p>\r
1116      *\r
1117      * Several forms of getters or setters are allowed. In all cases the\r
1118      * type of the value parameter can be any one of the following types: \r
1119      * Object, String, boolean, Scriptable, byte, short, int, long, float,\r
1120      * or double. The runtime will perform appropriate conversions based\r
1121      * upon the type of the parameter (see description in FunctionObject).\r
1122      * The first forms are nonstatic methods of the class referred to\r
1123      * by 'this':\r
1124      * <pre>\r
1125      * Object getFoo();\r
1126      * void setFoo(SomeType value);</pre>\r
1127      * Next are static methods that may be of any class; the object whose\r
1128      * property is being accessed is passed in as an extra argument:\r
1129      * <pre>\r
1130      * static Object getFoo(ScriptableObject obj);\r
1131      * static void setFoo(ScriptableObject obj, SomeType value);</pre>\r
1132      * Finally, it is possible to delegate to another object entirely using\r
1133      * the <code>delegateTo</code> parameter. In this case the methods are\r
1134      * nonstatic methods of the class delegated to, and the object whose\r
1135      * property is being accessed is passed in as an extra argument:\r
1136      * <pre>\r
1137      * Object getFoo(ScriptableObject obj);\r
1138      * void setFoo(ScriptableObject obj, SomeType value);</pre>\r
1139      *\r
1140      * @param propertyName the name of the property to define.\r
1141      * @param delegateTo an object to call the getter and setter methods on,\r
1142      *                   or null, depending on the form used above.\r
1143      * @param getter the method to invoke to get the value of the property\r
1144      * @param setter the method to invoke to set the value of the property\r
1145      * @param attributes the attributes of the JavaScript property\r
1146      * @exception PropertyException if the getter or setter\r
1147      *            do not conform to the forms specified above\r
1148      */\r
1149     public void defineProperty(String propertyName, Object delegateTo,\r
1150                                Method getter, Method setter, int attributes)\r
1151         throws PropertyException\r
1152     {\r
1153         int flags = Slot.HAS_GETTER;\r
1154         if (delegateTo == null && (Modifier.isStatic(getter.getModifiers())))\r
1155             delegateTo = HAS_STATIC_ACCESSORS;\r
1156         Class[] parmTypes = getter.getParameterTypes();\r
1157         if (parmTypes.length != 0) {\r
1158             if (parmTypes.length != 1 ||\r
1159                 parmTypes[0] != ScriptableObject.class)\r
1160             {\r
1161                 throw PropertyException.withMessage1\r
1162                     ("msg.bad.getter.parms", getter.toString());\r
1163             }\r
1164         } else if (delegateTo != null) {\r
1165             throw PropertyException.withMessage1\r
1166                 ("msg.obj.getter.parms", getter.toString());\r
1167         }\r
1168         if (setter != null) {\r
1169             flags |= Slot.HAS_SETTER;\r
1170             if ((delegateTo == HAS_STATIC_ACCESSORS) !=\r
1171                 (Modifier.isStatic(setter.getModifiers())))\r
1172             {\r
1173                 throw PropertyException.withMessage0("msg.getter.static");\r
1174             }\r
1175             parmTypes = setter.getParameterTypes();\r
1176             if (parmTypes.length == 2) {\r
1177                 if (parmTypes[0] != ScriptableObject.class) {\r
1178                     throw PropertyException.withMessage0("msg.setter2.parms");\r
1179                 }\r
1180                 if (delegateTo == null) {\r
1181                     throw PropertyException.withMessage1\r
1182                         ("msg.setter1.parms", setter.toString());\r
1183                 }\r
1184             } else if (parmTypes.length == 1) {\r
1185                 if (delegateTo != null) {\r
1186                     throw PropertyException.withMessage1\r
1187                         ("msg.setter2.expected", setter.toString());\r
1188                 }\r
1189             } else {\r
1190                 throw PropertyException.withMessage0("msg.setter.parms");\r
1191             }\r
1192         }\r
1193         GetterSlot slot = (GetterSlot)getSlotToSet(propertyName,\r
1194                                                    propertyName.hashCode(),\r
1195                                                    true);\r
1196         slot.delegateTo = delegateTo;\r
1197         slot.getter = getter;\r
1198         slot.setter = setter;\r
1199         slot.setterReturnsValue = setter != null && setter.getReturnType() != Void.TYPE;\r
1200         slot.value = null;\r
1201         slot.attributes = (short) attributes;\r
1202         slot.flags = (byte)flags;\r
1203     }\r
1204 \r
1205     /**\r
1206      * Search for names in a class, adding the resulting methods\r
1207      * as properties.\r
1208      *\r
1209      * <p> Uses reflection to find the methods of the given names. Then\r
1210      * FunctionObjects are constructed from the methods found, and\r
1211      * are added to this object as properties with the given names.\r
1212      *\r
1213      * @param names the names of the Methods to add as function properties\r
1214      * @param clazz the class to search for the Methods\r
1215      * @param attributes the attributes of the new properties\r
1216      * @exception PropertyException if any of the names\r
1217      *            has no corresponding method or more than one corresponding\r
1218      *            method in the class\r
1219      * @see org.mozilla.javascript.FunctionObject\r
1220      */\r
1221     public void defineFunctionProperties(String[] names, Class clazz,\r
1222                                          int attributes)\r
1223         throws PropertyException\r
1224     {\r
1225         for (int i=0; i < names.length; i++) {\r
1226             String name = names[i];\r
1227             Method[] m = FunctionObject.findMethods(clazz, name);\r
1228             if (m == null) {\r
1229                 throw PropertyException.withMessage2\r
1230                     ("msg.method.not.found", name, clazz.getName());\r
1231             }\r
1232             if (m.length > 1) {\r
1233                 throw PropertyException.withMessage2\r
1234                     ("msg.no.overload", name, clazz.getName());\r
1235             }\r
1236             FunctionObject f = new FunctionObject(name, m[0], this);\r
1237             defineProperty(name, f, attributes);\r
1238         }\r
1239     }\r
1240 \r
1241     /**\r
1242      * Get the Object.prototype property.\r
1243      * See ECMA 15.2.4.\r
1244      */\r
1245     public static Scriptable getObjectPrototype(Scriptable scope) {\r
1246         return getClassPrototype(scope, "Object");\r
1247     }\r
1248 \r
1249     /**\r
1250      * Get the Function.prototype property.\r
1251      * See ECMA 15.3.4.\r
1252      */\r
1253     public static Scriptable getFunctionPrototype(Scriptable scope) {\r
1254         return getClassPrototype(scope, "Function");\r
1255     }\r
1256 \r
1257     /**\r
1258      * Get the prototype for the named class.\r
1259      *\r
1260      * For example, <code>getClassPrototype(s, "Date")</code> will first\r
1261      * walk up the parent chain to find the outermost scope, then will\r
1262      * search that scope for the Date constructor, and then will\r
1263      * return Date.prototype. If any of the lookups fail, or\r
1264      * the prototype is not a JavaScript object, then null will\r
1265      * be returned.\r
1266      *\r
1267      * @param scope an object in the scope chain\r
1268      * @param className the name of the constructor\r
1269      * @return the prototype for the named class, or null if it\r
1270      *         cannot be found.\r
1271      */\r
1272     public static Scriptable getClassPrototype(Scriptable scope,\r
1273                                                String className)\r
1274     {\r
1275         scope = getTopLevelScope(scope);\r
1276         Object ctor = ScriptRuntime.getTopLevelProp(scope, className);\r
1277         if (ctor == NOT_FOUND || !(ctor instanceof Scriptable))\r
1278             return null;\r
1279         Scriptable ctorObj = (Scriptable) ctor;\r
1280         if (!ctorObj.has("prototype", ctorObj))\r
1281             return null;\r
1282         Object proto = ctorObj.get("prototype", ctorObj);\r
1283         if (!(proto instanceof Scriptable))\r
1284             return null;\r
1285         return (Scriptable) proto;\r
1286     }\r
1287 \r
1288     /**\r
1289      * Get the global scope.\r
1290      *\r
1291      * <p>Walks the parent scope chain to find an object with a null\r
1292      * parent scope (the global object).\r
1293      *\r
1294      * @param obj a JavaScript object\r
1295      * @return the corresponding global scope\r
1296      */\r
1297     public static Scriptable getTopLevelScope(Scriptable obj) {\r
1298         Scriptable next = obj;\r
1299         do {\r
1300             obj = next;\r
1301             next = obj.getParentScope();\r
1302         } while (next != null);\r
1303         return obj;\r
1304     }\r
1305     \r
1306     /**\r
1307      * Seal this object.\r
1308      * \r
1309      * A sealed object may not have properties added or removed. Once\r
1310      * an object is sealed it may not be unsealed.\r
1311      * \r
1312      * @since 1.4R3\r
1313      */\r
1314     public void sealObject() {\r
1315         count = -1;\r
1316     }\r
1317     \r
1318     /**\r
1319      * Return true if this object is sealed.\r
1320      * \r
1321      * It is an error to attempt to add or remove properties to \r
1322      * a sealed object.\r
1323      * \r
1324      * @return true if sealed, false otherwise.\r
1325      * @since 1.4R3\r
1326      */\r
1327     public boolean isSealed() {\r
1328         return count == -1;\r
1329     }\r
1330 \r
1331     /**\r
1332      * Gets a named property from an object or any object in its prototype chain.\r
1333      * <p>\r
1334      * Searches the prototype chain for a property named <code>name</code>.\r
1335      * <p>\r
1336      * @param obj a JavaScript object \r
1337      * @param name a property name \r
1338      * @return the value of a property with name <code>name</code> found in \r
1339      *         <code>obj</code> or any object in its prototype chain, or \r
1340      *         <code>Scriptable.NOT_FOUND</code> if not found\r
1341      * @since 1.5R2\r
1342      */\r
1343     public static Object getProperty(Scriptable obj, String name) {\r
1344         Scriptable start = obj;\r
1345         Object result;\r
1346         do {\r
1347             result = obj.get(name, start);\r
1348             if (result != Scriptable.NOT_FOUND)\r
1349                 break;\r
1350             obj = obj.getPrototype();\r
1351         } while (obj != null);\r
1352         return result;\r
1353     }\r
1354     \r
1355     /**\r
1356      * Gets an indexed property from an object or any object in its prototype chain.\r
1357      * <p>\r
1358      * Searches the prototype chain for a property with integral index \r
1359      * <code>index</code>. Note that if you wish to look for properties with numerical\r
1360      * but non-integral indicies, you should use getProperty(Scriptable,String) with\r
1361      * the string value of the index.\r
1362      * <p>\r
1363      * @param obj a JavaScript object \r
1364      * @param index an integral index \r
1365      * @return the value of a property with index <code>index</code> found in \r
1366      *         <code>obj</code> or any object in its prototype chain, or \r
1367      *         <code>Scriptable.NOT_FOUND</code> if not found\r
1368      * @since 1.5R2\r
1369      */\r
1370     public static Object getProperty(Scriptable obj, int index) {\r
1371         Scriptable start = obj;\r
1372         Object result;\r
1373         do {\r
1374             result = obj.get(index, start);\r
1375             if (result != Scriptable.NOT_FOUND)\r
1376                 break;\r
1377             obj = obj.getPrototype();\r
1378         } while (obj != null);\r
1379         return result;\r
1380     }\r
1381     \r
1382     /**\r
1383      * Returns whether a named property is defined in an object or any object \r
1384      * in its prototype chain.\r
1385      * <p>\r
1386      * Searches the prototype chain for a property named <code>name</code>.\r
1387      * <p>\r
1388      * @param obj a JavaScript object \r
1389      * @param name a property name \r
1390      * @return the true if property was found\r
1391      * @since 1.5R2\r
1392      */\r
1393     public static boolean hasProperty(Scriptable obj, String name) {\r
1394         Scriptable start = obj;\r
1395         do {\r
1396             if (obj.has(name, start))\r
1397                 return true;\r
1398             obj = obj.getPrototype();\r
1399         } while (obj != null);\r
1400         return false;\r
1401     }\r
1402     \r
1403     /**\r
1404      * Returns whether an indexed property is defined in an object or any object \r
1405      * in its prototype chain.\r
1406      * <p>\r
1407      * Searches the prototype chain for a property with index <code>index</code>.\r
1408      * <p>\r
1409      * @param obj a JavaScript object \r
1410      * @param index a property index \r
1411      * @return the true if property was found\r
1412      * @since 1.5R2\r
1413      */\r
1414     public static boolean hasProperty(Scriptable obj, int index) {\r
1415         Scriptable start = obj;\r
1416         do {\r
1417             if (obj.has(index, start))\r
1418                 return true;\r
1419             obj = obj.getPrototype();\r
1420         } while (obj != null);\r
1421         return false;\r
1422     }\r
1423 \r
1424     /**\r
1425      * Puts a named property in an object or in an object in its prototype chain.\r
1426      * <p>\r
1427      * Seaches for the named property in the prototype chain. If it is found,\r
1428      * the value of the property is changed. If it is not found, a new\r
1429      * property is added in <code>obj</code>.\r
1430      * @param obj a JavaScript object \r
1431      * @param name a property name\r
1432      * @param value any JavaScript value accepted by Scriptable.put \r
1433      * @since 1.5R2\r
1434      */\r
1435     public static void putProperty(Scriptable obj, String name, Object value) {\r
1436         Scriptable base = getBase(obj, name);\r
1437         if (base == null)\r
1438             base = obj;\r
1439         base.put(name, obj, value);\r
1440     }\r
1441 \r
1442     /**\r
1443      * Puts an indexed property in an object or in an object in its prototype chain.\r
1444      * <p>\r
1445      * Seaches for the indexed property in the prototype chain. If it is found,\r
1446      * the value of the property is changed. If it is not found, a new\r
1447      * property is added in <code>obj</code>.\r
1448      * @param obj a JavaScript object \r
1449      * @param index a property index\r
1450      * @param value any JavaScript value accepted by Scriptable.put \r
1451      * @since 1.5R2\r
1452      */\r
1453     public static void putProperty(Scriptable obj, int index, Object value) {\r
1454         Scriptable base = getBase(obj, index);\r
1455         if (base == null)\r
1456             base = obj;\r
1457         base.put(index, obj, value);\r
1458     }\r
1459 \r
1460     /**\r
1461      * Removes the property from an object or its prototype chain.\r
1462      * <p>\r
1463      * Searches for a property with <code>name</code> in obj or\r
1464      * its prototype chain. If it is found, the object's delete\r
1465      * method is called. \r
1466      * @param obj a JavaScript object\r
1467      * @param name a property name\r
1468      * @return true if the property doesn't exist or was successfully removed\r
1469      * @since 1.5R2\r
1470      */\r
1471     public static boolean deleteProperty(Scriptable obj, String name) {\r
1472         Scriptable base = getBase(obj, name);\r
1473         if (base == null)\r
1474             return true;\r
1475         base.delete(name);\r
1476         return base.get(name, obj) == NOT_FOUND;\r
1477     }\r
1478                 \r
1479     /**\r
1480      * Removes the property from an object or its prototype chain.\r
1481      * <p>\r
1482      * Searches for a property with <code>index</code> in obj or\r
1483      * its prototype chain. If it is found, the object's delete\r
1484      * method is called. \r
1485      * @param obj a JavaScript object\r
1486      * @param index a property index\r
1487      * @return true if the property doesn't exist or was successfully removed\r
1488      * @since 1.5R2\r
1489      */\r
1490     public static boolean deleteProperty(Scriptable obj, int index) {\r
1491         Scriptable base = getBase(obj, index);\r
1492         if (base == null)\r
1493             return true;\r
1494         base.delete(index);\r
1495         return base.get(index, obj) == NOT_FOUND;\r
1496     }\r
1497     \r
1498     /**\r
1499      * Returns an array of all ids from an object and its prototypes.\r
1500      * <p>\r
1501      * @param obj a JavaScript object\r
1502      * @return an array of all ids from all object in the prototype chain.\r
1503      *         If a given id occurs multiple times in the prototype chain,\r
1504      *         it will occur only once in this list.\r
1505      * @since 1.5R2\r
1506      */\r
1507     public static Object[] getPropertyIds(Scriptable obj) {\r
1508         Hashtable h = new Hashtable();  // JDK1.2: use HashSet\r
1509         while (obj != null) {\r
1510             Object[] ids = obj.getIds();\r
1511             for (int i=0; i < ids.length; i++) {\r
1512                 h.put(ids[i], ids[i]);\r
1513             }\r
1514             obj = (Scriptable)obj.getPrototype();\r
1515         }\r
1516         Object[] result = new Object[h.size()];\r
1517         java.util.Enumeration e = h.elements();\r
1518         int n = 0;\r
1519         while (e.hasMoreElements()) {\r
1520             result[n++] = e.nextElement();\r
1521         }\r
1522         return result;\r
1523     }\r
1524     \r
1525     /**\r
1526      * Call a method of an object.\r
1527      * <p>\r
1528      * @param obj the JavaScript object\r
1529      * @param methodName the name of the function property\r
1530      * @param args the arguments for the call\r
1531      * @exception JavaScriptException thrown if there were errors in the call\r
1532      */\r
1533     public static Object callMethod(Scriptable obj, String methodName, \r
1534                                     Object[] args)\r
1535         throws JavaScriptException\r
1536     {\r
1537         Context cx = Context.enter();\r
1538         try {\r
1539             Object fun = getProperty(obj, methodName);\r
1540             if (fun == NOT_FOUND)\r
1541                 fun = Undefined.instance;\r
1542             return ScriptRuntime.call(cx, fun, obj, args, getTopLevelScope(obj));\r
1543         } finally {\r
1544           Context.exit();\r
1545         }\r
1546     }\r
1547                 \r
1548     private static Scriptable getBase(Scriptable obj, String s) {\r
1549         Scriptable m = obj;\r
1550         while (m != null) {\r
1551             if (m.has(s, obj))\r
1552                 return m;\r
1553             m = m.getPrototype();\r
1554         }\r
1555         return null;\r
1556     }\r
1557 \r
1558     private static Scriptable getBase(Scriptable obj, int index) {\r
1559         Scriptable m = obj;\r
1560         while (m != null) {\r
1561             if (m.has(index, obj))\r
1562                 return m;\r
1563             m = m.getPrototype();\r
1564         }\r
1565         return null;\r
1566     }\r
1567     \r
1568     /**\r
1569      * Adds a property attribute to all properties.\r
1570      */\r
1571     synchronized void addPropertyAttribute(int attribute) {\r
1572         if (slots == null)\r
1573             return;\r
1574         for (int i=0; i < slots.length; i++) {\r
1575             Slot slot = slots[i];\r
1576             if (slot == null || slot == REMOVED)\r
1577                 continue;\r
1578             if ((slot.flags & slot.HAS_SETTER) != 0 && attribute == READONLY)\r
1579                 continue;\r
1580             slot.attributes |= attribute;\r
1581         }\r
1582     }\r
1583     \r
1584     private Slot getSlot(String id, int index, boolean shouldDelete) {\r
1585         Slot[] slots = this.slots;\r
1586         if (slots == null)\r
1587             return null;\r
1588         int start = (index & 0x7fffffff) % slots.length;\r
1589         int i = start;\r
1590         do {\r
1591             Slot slot = slots[i];\r
1592             if (slot == null)\r
1593                 return null;\r
1594             if (slot != REMOVED && slot.intKey == index && \r
1595                 (slot.stringKey == id || (id != null && \r
1596                                           id.equals(slot.stringKey))))\r
1597             {\r
1598                 if (shouldDelete) {\r
1599                     if ((slot.attributes & PERMANENT) == 0) {\r
1600                         // Mark the slot as removed to handle a case when\r
1601                         // another thread manages to put just removed slot\r
1602                         // into lastAccess cache.\r
1603                         slot.wasDeleted = (byte)1;\r
1604                         slots[i] = REMOVED;\r
1605                         count--;\r
1606                         if (slot == lastAccess)\r
1607                             lastAccess = REMOVED;\r
1608                     }\r
1609                 }\r
1610                 return slot;\r
1611             }\r
1612             if (++i == slots.length)\r
1613                 i = 0;\r
1614         } while (i != start);\r
1615         return null;\r
1616     }\r
1617 \r
1618     private Slot getSlotToSet(String id, int index, boolean getterSlot) {\r
1619         if (slots == null)\r
1620             slots = new Slot[5];\r
1621         int start = (index & 0x7fffffff) % slots.length;\r
1622         boolean sawRemoved = false;\r
1623         int i = start;\r
1624         do {\r
1625             Slot slot = slots[i];\r
1626             if (slot == null) {\r
1627                 return addSlot(id, index, getterSlot);\r
1628             }\r
1629             if (slot == REMOVED) {\r
1630                 sawRemoved = true;\r
1631             } else if (slot.intKey == index && \r
1632                        (slot.stringKey == id || \r
1633                         (id != null && id.equals(slot.stringKey))))\r
1634             {\r
1635                 return slot;\r
1636             }\r
1637             if (++i == slots.length)\r
1638                 i = 0;\r
1639         } while (i != start);\r
1640         if (sawRemoved) {\r
1641             // Table could be full, but with some REMOVED elements. \r
1642             // Call to addSlot will use a slot currently taken by \r
1643             // a REMOVED.\r
1644             return addSlot(id, index, getterSlot);\r
1645         }\r
1646         throw new RuntimeException("Hashtable internal error");\r
1647     }\r
1648 \r
1649     /**\r
1650      * Add a new slot to the hash table.\r
1651      *\r
1652      * This method must be synchronized since it is altering the hash\r
1653      * table itself. Note that we search again for the slot to set\r
1654      * since another thread could have added the given property or\r
1655      * caused the table to grow while this thread was searching.\r
1656      */\r
1657     private synchronized Slot addSlot(String id, int index, boolean getterSlot)\r
1658     {\r
1659         if (count == -1)\r
1660             throw Context.reportRuntimeError0("msg.add.sealed");\r
1661         int start = (index & 0x7fffffff) % slots.length;\r
1662         int i = start;\r
1663         do {\r
1664             Slot slot = slots[i];\r
1665             if (slot == null || slot == REMOVED) {\r
1666                 if ((4 * (count+1)) > (3 * slots.length)) {\r
1667                     grow();\r
1668                     return getSlotToSet(id, index, getterSlot);\r
1669                 }\r
1670                 slot = getterSlot ? new GetterSlot() : new Slot();\r
1671                 slot.stringKey = id;\r
1672                 slot.intKey = index;\r
1673                 slots[i] = slot;\r
1674                 count++;\r
1675                 return slot;\r
1676             }\r
1677             if (slot.intKey == index && \r
1678                 (slot.stringKey == id || (id != null && \r
1679                                           id.equals(slot.stringKey)))) \r
1680             {\r
1681                 return slot;\r
1682             }\r
1683             if (++i == slots.length)\r
1684                 i = 0;\r
1685         } while (i != start);\r
1686         throw new RuntimeException("Hashtable internal error");\r
1687     }\r
1688 \r
1689     /**\r
1690      * Remove a slot from the hash table.\r
1691      *\r
1692      * This method must be synchronized since it is altering the hash\r
1693      * table itself. We might be able to optimize this more, but\r
1694      * deletes are not common.\r
1695      */\r
1696     private synchronized void removeSlot(String name, int index) {\r
1697         if (count == -1)\r
1698             throw Context.reportRuntimeError0("msg.remove.sealed");\r
1699         getSlot(name, index, true);\r
1700     }\r
1701 \r
1702     /**\r
1703      * Grow the hash table to accommodate new entries.\r
1704      *\r
1705      * Note that by assigning the new array back at the end we\r
1706      * can continue reading the array from other threads.\r
1707      */\r
1708     private synchronized void grow() {\r
1709         Slot[] newSlots = new Slot[slots.length*2 + 1];\r
1710         for (int j=slots.length-1; j >= 0 ; j--) {\r
1711             Slot slot = slots[j];\r
1712             if (slot == null || slot == REMOVED)\r
1713                 continue;\r
1714             int k = (slot.intKey & 0x7fffffff) % newSlots.length;\r
1715             while (newSlots[k] != null)\r
1716                 if (++k == newSlots.length)\r
1717                     k = 0;\r
1718             // The end of the "synchronized" statement will cause the memory\r
1719             // writes to be propagated on a multiprocessor machine. We want\r
1720             // to make sure that the new table is prepared to be read.\r
1721             // XXX causes the 'this' pointer to be null in calling stack frames\r
1722             // on the MS JVM\r
1723             //synchronized (slot) { }\r
1724             newSlots[k] = slot;\r
1725         }\r
1726         slots = newSlots;\r
1727     }\r
1728 \r
1729     private static Hashtable getExclusionList() {\r
1730         if (exclusionList != null)\r
1731             return exclusionList;\r
1732         Hashtable result = new Hashtable(17);\r
1733         Method[] methods = ScriptRuntime.FunctionClass.getMethods();\r
1734         for (int i=0; i < methods.length; i++) {\r
1735             result.put(methods[i].getName(), Boolean.TRUE);\r
1736         }\r
1737         exclusionList = result;\r
1738         return result;\r
1739     }\r
1740         \r
1741     Object[] getIds(boolean getAll) {\r
1742         Slot[] s = slots;\r
1743         Object[] a = ScriptRuntime.emptyArgs;\r
1744         if (s == null)\r
1745             return a;\r
1746         int c = 0;\r
1747         for (int i=0; i < s.length; i++) {\r
1748             Slot slot = s[i];\r
1749             if (slot == null || slot == REMOVED)\r
1750                 continue;\r
1751             if (getAll || (slot.attributes & DONTENUM) == 0) {\r
1752                 if (c == 0)\r
1753                     a = new Object[s.length - i];\r
1754                 a[c++] = slot.stringKey != null\r
1755                              ? (Object) slot.stringKey\r
1756                              : new Integer(slot.intKey);\r
1757             }\r
1758         }\r
1759         if (c == a.length)\r
1760             return a;\r
1761         Object[] result = new Object[c];\r
1762         System.arraycopy(a, 0, result, 0, c);\r
1763         return result;\r
1764     }\r
1765 \r
1766     \r
1767     /**\r
1768      * The prototype of this object.\r
1769      */\r
1770     protected Scriptable prototype;\r
1771     \r
1772     /**\r
1773      * The parent scope of this object.\r
1774      */\r
1775     protected Scriptable parent;\r
1776 \r
1777     private static final Object HAS_STATIC_ACCESSORS = Void.TYPE;\r
1778     private static final Slot REMOVED = new Slot();\r
1779     private static Hashtable exclusionList = null;\r
1780     \r
1781     private Slot[] slots;\r
1782     private int count;\r
1783 \r
1784     // cache; may be removed for smaller memory footprint\r
1785     private Slot lastAccess = REMOVED;\r
1786 \r
1787     private static class Slot {\r
1788         static final int HAS_GETTER  = 0x01;\r
1789         static final int HAS_SETTER  = 0x02;\r
1790         \r
1791         int intKey;\r
1792         String stringKey;\r
1793         Object value;\r
1794         short attributes;\r
1795         byte flags;\r
1796         byte wasDeleted;\r
1797     }\r
1798 \r
1799     private static class GetterSlot extends Slot {\r
1800         Object delegateTo;  // OPT: merge with "value"\r
1801         Method getter;\r
1802         Method setter;\r
1803         boolean setterReturnsValue;\r
1804     }\r
1805 \r
1806     private static final Class ContextClass = Context.class;\r
1807 }\r