~ubuntu-branches/ubuntu/karmic/rhino/karmic

« back to all changes in this revision

Viewing changes to src/org/mozilla/javascript/ScriptableObject.java

  • Committer: Bazaar Package Importer
  • Author(s): Jerry Haltom
  • Date: 2005-03-19 16:56:07 UTC
  • mto: (11.1.1 squeeze)
  • mto: This revision was merged to the branch mainline in revision 3.
  • Revision ID: james.westby@ubuntu.com-20050319165607-geu3j3fnqlkpqkh1
Tags: upstream-1.6.R1
ImportĀ upstreamĀ versionĀ 1.6.R1

Show diffs side-by-side

added added

removed removed

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