1
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/
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.
13
* The Original Code is Rhino code, released
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
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.
40
package org.mozilla.javascript;
42
import java.lang.reflect.*;
43
import java.util.Hashtable;
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.
50
* Various properties and methods of JavaScript objects can be conveniently
51
* defined using methods of ScriptableObject.
53
* Classes extending ScriptableObject must define the getClassName method.
55
* @see org.mozilla.javascript.Scriptable
59
public abstract class ScriptableObject implements Scriptable {
62
* The empty property attribute.
64
* Used by getAttributes() and setAttributes().
66
* @see org.mozilla.javascript.ScriptableObject#getAttributes
67
* @see org.mozilla.javascript.ScriptableObject#setAttributes
69
public static final int EMPTY = 0x00;
72
* Property attribute indicating assignment to this property is ignored.
74
* @see org.mozilla.javascript.ScriptableObject#put
75
* @see org.mozilla.javascript.ScriptableObject#getAttributes
76
* @see org.mozilla.javascript.ScriptableObject#setAttributes
78
public static final int READONLY = 0x01;
81
* Property attribute indicating property is not enumerated.
83
* Only enumerated properties will be returned by getIds().
85
* @see org.mozilla.javascript.ScriptableObject#getIds
86
* @see org.mozilla.javascript.ScriptableObject#getAttributes
87
* @see org.mozilla.javascript.ScriptableObject#setAttributes
89
public static final int DONTENUM = 0x02;
92
* Property attribute indicating property cannot be deleted.
94
* @see org.mozilla.javascript.ScriptableObject#delete
95
* @see org.mozilla.javascript.ScriptableObject#getAttributes
96
* @see org.mozilla.javascript.ScriptableObject#setAttributes
98
public static final int PERMANENT = 0x04;
101
* Return the name of the class.
103
* This is typically the same name as the constructor.
104
* Classes extending ScriptableObject must implement this abstract
107
public abstract String getClassName();
110
* Returns true if the named property is defined.
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
116
public boolean has(String name, Scriptable start) {
117
return getSlot(name, name.hashCode(), false) != null;
121
* Returns true if the property index is defined.
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
127
public boolean has(int index, Scriptable start) {
128
return getSlot(null, index, false) != null;
132
* Returns the value of the named property or NOT_FOUND.
134
* If the property was created using defineProperty, the
135
* appropriate getter method is called.
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
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; }
146
int hashCode = name.hashCode();
147
slot = getSlot(name, hashCode, false);
149
return Scriptable.NOT_FOUND;
150
if ((slot.flags & Slot.HAS_GETTER) != 0) {
151
GetterSlot getterSlot = (GetterSlot) slot;
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();
164
return getterSlot.getter.invoke(start, ScriptRuntime.emptyArgs);
166
Object[] args = { this };
167
return getterSlot.getter.invoke(getterSlot.delegateTo, args);
169
catch (InvocationTargetException e) {
170
throw WrappedException.wrapException(e);
172
catch (IllegalAccessException e) {
173
throw WrappedException.wrapException(e);
176
// Here stringKey.equals(name) holds, but it can be that
177
// slot.stringKey != name. To make last name cache work, need
179
slot.stringKey = name;
187
* Returns the value of the indexed property or NOT_FOUND.
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
193
public Object get(int index, Scriptable start) {
194
Slot slot = getSlot(null, index, false);
196
return Scriptable.NOT_FOUND;
201
* Sets the value of the named property, creating it if need be.
203
* If the property was created using defineProperty, the
204
* appropriate setter method is called. <p>
206
* If the property's attributes include READONLY, no action is
208
* This method will actually set the property in the start
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
215
public void put(String name, Scriptable start, Object value) {
216
int hash = name.hashCode();
217
Slot slot = getSlot(name, hash, false);
220
start.put(name, start, value);
223
slot = getSlotToSet(name, hash, false);
225
if ((slot.attributes & ScriptableObject.READONLY) != 0)
227
if ((slot.flags & Slot.HAS_SETTER) != 0) {
228
GetterSlot getterSlot = (GetterSlot) slot;
230
Class pTypes[] = getterSlot.setter.getParameterTypes();
231
Class desired = pTypes[pTypes.length - 1];
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();
246
Object v = getterSlot.setter.invoke(start, arg);
247
if (getterSlot.setterReturnsValue) {
249
if (!(v instanceof Method))
254
Object[] args = { this, actualArg };
255
Object v = getterSlot.setter.invoke(getterSlot.delegateTo, args);
256
if (getterSlot.setterReturnsValue) {
258
if (!(v instanceof Method))
263
catch (InvocationTargetException e) {
264
throw WrappedException.wrapException(e);
266
catch (IllegalAccessException e) {
267
throw WrappedException.wrapException(e);
273
slot.stringKey = name;
276
start.put(name, start, value);
281
* Sets the value of the indexed property, creating it if need be.
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
287
public void put(int index, Scriptable start, Object value) {
288
Slot slot = getSlot(null, index, false);
291
start.put(index, start, value);
294
slot = getSlotToSet(null, index, false);
296
if ((slot.attributes & ScriptableObject.READONLY) != 0)
301
start.put(index, start, value);
306
* Removes a named property from the object.
308
* If the property is not found, or it has the PERMANENT attribute,
309
* no action is taken.
311
* @param name the name of the property
313
public void delete(String name) {
314
removeSlot(name, name.hashCode());
318
* Removes the indexed property from the object.
320
* If the property is not found, or it has the PERMANENT attribute,
321
* no action is taken.
323
* @param index the numeric index for the property
325
public void delete(int index) {
326
removeSlot(null, index);
330
* Get the attributes of a named property.
332
* The property is specified by <code>name</code>
333
* as defined for <code>has</code>.<p>
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
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
346
public int getAttributes(String name, Scriptable start)
347
throws PropertyException
349
Slot slot = getSlot(name, name.hashCode(), false);
351
throw PropertyException.withMessage0("msg.prop.not.found");
353
return slot.attributes;
357
* Get the attributes of an indexed property.
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
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
370
public int getAttributes(int index, Scriptable start)
371
throws PropertyException
373
Slot slot = getSlot(null, index, false);
375
throw PropertyException.withMessage0("msg.prop.not.found");
377
return slot.attributes;
381
* Set the attributes of a named property.
383
* The property is specified by <code>name</code>
384
* as defined for <code>has</code>.<p>
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.
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
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
403
public void setAttributes(String name, Scriptable start,
405
throws PropertyException
407
final int mask = READONLY | DONTENUM | PERMANENT;
408
attributes &= mask; // mask out unused bits
409
Slot slot = getSlot(name, name.hashCode(), false);
411
throw PropertyException.withMessage0("msg.prop.not.found");
413
slot.attributes = (short) attributes;
417
* Set the attributes of an indexed property.
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
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
430
public void setAttributes(int index, Scriptable start,
432
throws PropertyException
434
Slot slot = getSlot(null, index, false);
436
throw PropertyException.withMessage0("msg.prop.not.found");
438
slot.attributes = (short) attributes;
442
* Returns the prototype of the object.
444
public Scriptable getPrototype() {
449
* Sets the prototype of the object.
451
public void setPrototype(Scriptable m) {
456
* Returns the parent (enclosing) scope of the object.
458
public Scriptable getParentScope() {
463
* Sets the parent (enclosing) scope of the object.
465
public void setParentScope(Scriptable m) {
470
* Returns an array of ids for the properties of the object.
472
* <p>Any properties with the attribute DONTENUM are not listed. <p>
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.
480
public Object[] getIds() {
481
return getIds(false);
485
* Returns an array of ids for the properties of the object.
487
* <p>All properties, even those with attribute DONTENUM, are listed. <p>
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.
495
public Object[] getAllIds() {
500
* Implements the [[DefaultValue]] internal method.
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>
506
* A <code>hint</code> of null means "no hint".
508
* @param typeHint the type hint
509
* @return the default value for the object
513
public Object getDefaultValue(Class typeHint) {
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))
522
Function fun = (Function) v;
524
cx = Context.getContext();
525
val = fun.call(cx, fun.getParentScope(), this,
526
ScriptRuntime.emptyArgs);
529
if (typeHint == null)
531
else if (typeHint == ScriptRuntime.StringClass)
533
else if (typeHint == ScriptRuntime.ScriptableClass)
535
else if (typeHint == ScriptRuntime.FunctionClass)
537
else if (typeHint == ScriptRuntime.BooleanClass ||
538
typeHint == Boolean.TYPE)
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)
553
throw Context.reportRuntimeError1(
554
"msg.invalid.type", typeHint.toString());
556
Object v = getProperty(this, "valueOf");
557
if (!(v instanceof Function))
559
Function fun = (Function) v;
560
Object[] args = { hint };
562
cx = Context.getContext();
563
val = fun.call(cx, fun.getParentScope(), this, args);
565
if (val != null && (val == Undefined.instance ||
566
!(val instanceof Scriptable) ||
567
typeHint == Scriptable.class ||
568
typeHint == Function.class))
572
if (val instanceof NativeJavaObject) {
573
// Let a wrapped java.lang.String pass for a primitive
575
Object u = ((Wrapper) val).unwrap();
576
if (u instanceof String)
580
// fall through to error
582
catch (JavaScriptException jse) {
583
// fall through to error
585
Object arg = (typeHint == null) ? "undefined" : typeHint.toString();
586
throw NativeGlobal.typeError1("msg.default.value", arg, this);
590
* Implements the instanceof operator.
592
* <p>This operator has been proposed to ECMA.
594
* @param instance The value that appeared on the LHS of the instanceof
596
* @return true if "this" appears in value's prototype chain
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.
603
return ScriptRuntime.jsDelegatesTo(instance, this);
607
* Defines JavaScript objects from a Java class that implements Scriptable.
609
* If the given class has a method
611
* static void init(Context cx, Scriptable scope, boolean sealed);</pre>
613
* or its compatibility form
615
* static void init(Scriptable scope);</pre>
617
* then it is invoked and no further initialization is done.<p>
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>
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>
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
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
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>
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.
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>
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
673
* Finally, if there is a method
675
* static void finishInit(Scriptable scope, FunctionObject constructor,
676
* Scriptable prototype)</pre>
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>
682
* @param scope The scope in which to define the constructor
683
* @param clazz The Java class to use to define the JavaScript objects
685
* @exception IllegalAccessException if access is not available
686
* to a reflected class member
687
* @exception InstantiationException if unable to instantiate
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
701
public static void defineClass(Scriptable scope, Class clazz)
702
throws IllegalAccessException, InstantiationException,
703
InvocationTargetException, ClassDefinitionException,
706
defineClass(scope, clazz, false);
710
* Defines JavaScript objects from a Java class, optionally
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
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
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
737
public static void defineClass(Scriptable scope, Class clazz,
739
throws IllegalAccessException, InstantiationException,
740
InvocationTargetException, ClassDefinitionException,
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"))
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()))
755
Object args[] = { Context.getContext(), scope,
756
sealed ? Boolean.TRUE : Boolean.FALSE };
757
method.invoke(null, args);
760
if (parmTypes.length == 1 &&
761
parmTypes[0] == ScriptRuntime.ScriptableClass &&
762
Modifier.isStatic(method.getModifiers()))
764
Object args[] = { scope };
765
method.invoke(null, args);
771
// If we got here, there isn't an "init" method with the right
773
Hashtable exclusionList = getExclusionList();
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];
783
if (protoCtor == null) {
784
throw new ClassDefinitionException(
785
Context.getMessage1("msg.zero.arg.ctor", clazz.getName()));
788
Scriptable proto = (Scriptable)
789
protoCtor.newInstance(ScriptRuntime.emptyArgs);
790
proto.setPrototype(getObjectPrototype(scope));
791
String className = proto.getClassName();
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";
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]));
814
ctorMember = ctorMeths[0];
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
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) {
839
name = name.substring(prefix.length());
841
if (name.equals(className)) {
842
if (ctorMember != null) {
843
throw new ClassDefinitionException(
844
Context.getMessage2("msg.multiple.ctors",
845
ctorMember, methods[i]));
847
ctorMember = methods[i];
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];
860
if (ctorMember == null) {
861
throw new ClassDefinitionException(
862
Context.getMessage1("msg.ctor.multiple.parms",
867
FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
868
if (ctor.isVarArgsMethod()) {
869
throw Context.reportRuntimeError1
870
("msg.varargs.ctor", ctorMember.getName());
872
ctor.addAsConstructor(scope, proto);
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)
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()))
889
finishInit = methods[i];
893
// ignore any compiler generated methods.
894
if (name.indexOf('$') != -1)
896
if (name.equals(ctorName))
898
String prefix = null;
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.");
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;
919
name = name.substring(prefix.length());
920
} else if (exclusionList.get(name) != null)
922
if (methods[i] == ctorMember) {
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);
932
Method[] setter = FunctionObject.findMethods(
934
setterPrefix + name);
935
if (setter != null && setter.length != 1) {
936
throw PropertyException.withMessage2
937
("msg.no.overload", name, clazz.getName());
939
int attr = ScriptableObject.PERMANENT |
940
ScriptableObject.DONTENUM |
942
: ScriptableObject.READONLY);
943
Method m = setter == null ? null : setter[0];
944
((ScriptableObject) proto).defineProperty(name, null,
949
if ((name.startsWith("get") || name.startsWith("set")) &&
951
!(hasPrefix && (prefix.equals(functionPrefix) ||
952
prefix.equals(staticFunctionPrefix))))
954
if (!(proto instanceof ScriptableObject)) {
955
throw PropertyException.withMessage2
956
("msg.extend.scriptable",
957
proto.getClass().toString(), name);
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();
968
buf.insert(0, "set");
969
String setterName = buf.toString();
970
Method[] setter = FunctionObject.findMethods(
972
hasPrefix ? genericPrefix + setterName
974
if (setter != null && setter.length != 1) {
975
throw PropertyException.withMessage2
976
("msg.no.overload", name, clazz.getName());
978
if (setter == null && hasPrefix)
979
setter = FunctionObject.findMethods(
981
propertyPrefix + setterName);
982
int attr = ScriptableObject.PERMANENT |
983
ScriptableObject.DONTENUM |
985
: ScriptableObject.READONLY);
986
Method m = setter == null ? null : setter[0];
987
((ScriptableObject) proto).defineProperty(propertyName, null,
992
FunctionObject f = new FunctionObject(name, methods[i], proto);
993
if (f.isVarArgsConstructor()) {
994
throw Context.reportRuntimeError1
995
("msg.varargs.fun", ctorMember.getName());
997
Scriptable dest = prefix == staticFunctionPrefix
1000
defineProperty(dest, name, f, DONTENUM);
1003
f.addPropertyAttribute(READONLY);
1007
if (finishInit != null) {
1008
// call user code to complete the initialization
1009
Object[] finishArgs = { scope, ctor, proto };
1010
finishInit.invoke(null, finishArgs);
1015
ctor.addPropertyAttribute(READONLY);
1016
if (proto instanceof ScriptableObject) {
1017
((ScriptableObject) proto).sealObject();
1018
((ScriptableObject) proto).addPropertyAttribute(READONLY);
1024
* Define a JavaScript property.
1026
* Creates the property with an initial value and sets its attributes.
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
1033
public void defineProperty(String propertyName, Object value,
1036
put(propertyName, this, value);
1038
setAttributes(propertyName, this, attributes);
1040
catch (PropertyException e) {
1041
throw new RuntimeException("Cannot create property");
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
1051
public static void defineProperty(Scriptable destination,
1052
String propertyName, Object value,
1055
if (destination instanceof ScriptableObject) {
1056
ScriptableObject obj = (ScriptableObject)destination;
1057
obj.defineProperty(propertyName, value, attributes);
1060
destination.put(propertyName, destination, value);
1065
* Define a JavaScript property with getter and setter side effects.
1067
* If the setter is not found, the attribute READONLY is added to
1068
* the given attributes. <p>
1070
* The getter must be a method with zero parameters, and the setter, if
1071
* found, must be a method with one parameter.<p>
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,
1085
* @see org.mozilla.javascript.Scriptable#put
1087
public void defineProperty(String propertyName, Class clazz,
1089
throws PropertyException
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);
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());
1102
defineProperty(propertyName, null, getter[0],
1103
setter == null ? null : setter[0], attributes);
1107
* Define a JavaScript property.
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>.
1114
* If <code>setter</code> is null, the attribute READONLY is added to
1115
* the given attributes.<p>
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
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:
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:
1137
* Object getFoo(ScriptableObject obj);
1138
* void setFoo(ScriptableObject obj, SomeType value);</pre>
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
1149
public void defineProperty(String propertyName, Object delegateTo,
1150
Method getter, Method setter, int attributes)
1151
throws PropertyException
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)
1161
throw PropertyException.withMessage1
1162
("msg.bad.getter.parms", getter.toString());
1164
} else if (delegateTo != null) {
1165
throw PropertyException.withMessage1
1166
("msg.obj.getter.parms", getter.toString());
1168
if (setter != null) {
1169
flags |= Slot.HAS_SETTER;
1170
if ((delegateTo == HAS_STATIC_ACCESSORS) !=
1171
(Modifier.isStatic(setter.getModifiers())))
1173
throw PropertyException.withMessage0("msg.getter.static");
1175
parmTypes = setter.getParameterTypes();
1176
if (parmTypes.length == 2) {
1177
if (parmTypes[0] != ScriptableObject.class) {
1178
throw PropertyException.withMessage0("msg.setter2.parms");
1180
if (delegateTo == null) {
1181
throw PropertyException.withMessage1
1182
("msg.setter1.parms", setter.toString());
1184
} else if (parmTypes.length == 1) {
1185
if (delegateTo != null) {
1186
throw PropertyException.withMessage1
1187
("msg.setter2.expected", setter.toString());
1190
throw PropertyException.withMessage0("msg.setter.parms");
1193
GetterSlot slot = (GetterSlot)getSlotToSet(propertyName,
1194
propertyName.hashCode(),
1196
slot.delegateTo = delegateTo;
1197
slot.getter = getter;
1198
slot.setter = setter;
1199
slot.setterReturnsValue = setter != null && setter.getReturnType() != Void.TYPE;
1201
slot.attributes = (short) attributes;
1202
slot.flags = (byte)flags;
1206
* Search for names in a class, adding the resulting methods
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.
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
1221
public void defineFunctionProperties(String[] names, Class clazz,
1223
throws PropertyException
1225
for (int i=0; i < names.length; i++) {
1226
String name = names[i];
1227
Method[] m = FunctionObject.findMethods(clazz, name);
1229
throw PropertyException.withMessage2
1230
("msg.method.not.found", name, clazz.getName());
1233
throw PropertyException.withMessage2
1234
("msg.no.overload", name, clazz.getName());
1236
FunctionObject f = new FunctionObject(name, m[0], this);
1237
defineProperty(name, f, attributes);
1242
* Get the Object.prototype property.
1245
public static Scriptable getObjectPrototype(Scriptable scope) {
1246
return getClassPrototype(scope, "Object");
1250
* Get the Function.prototype property.
1253
public static Scriptable getFunctionPrototype(Scriptable scope) {
1254
return getClassPrototype(scope, "Function");
1258
* Get the prototype for the named class.
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
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
1272
public static Scriptable getClassPrototype(Scriptable scope,
1275
scope = getTopLevelScope(scope);
1276
Object ctor = ScriptRuntime.getTopLevelProp(scope, className);
1277
if (ctor == NOT_FOUND || !(ctor instanceof Scriptable))
1279
Scriptable ctorObj = (Scriptable) ctor;
1280
if (!ctorObj.has("prototype", ctorObj))
1282
Object proto = ctorObj.get("prototype", ctorObj);
1283
if (!(proto instanceof Scriptable))
1285
return (Scriptable) proto;
1289
* Get the global scope.
1291
* <p>Walks the parent scope chain to find an object with a null
1292
* parent scope (the global object).
1294
* @param obj a JavaScript object
1295
* @return the corresponding global scope
1297
public static Scriptable getTopLevelScope(Scriptable obj) {
1298
Scriptable next = obj;
1301
next = obj.getParentScope();
1302
} while (next != null);
1309
* A sealed object may not have properties added or removed. Once
1310
* an object is sealed it may not be unsealed.
1314
public void sealObject() {
1319
* Return true if this object is sealed.
1321
* It is an error to attempt to add or remove properties to
1324
* @return true if sealed, false otherwise.
1327
public boolean isSealed() {
1332
* Gets a named property from an object or any object in its prototype chain.
1334
* Searches the prototype chain for a property named <code>name</code>.
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
1343
public static Object getProperty(Scriptable obj, String name) {
1344
Scriptable start = obj;
1347
result = obj.get(name, start);
1348
if (result != Scriptable.NOT_FOUND)
1350
obj = obj.getPrototype();
1351
} while (obj != null);
1356
* Gets an indexed property from an object or any object in its prototype chain.
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.
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
1370
public static Object getProperty(Scriptable obj, int index) {
1371
Scriptable start = obj;
1374
result = obj.get(index, start);
1375
if (result != Scriptable.NOT_FOUND)
1377
obj = obj.getPrototype();
1378
} while (obj != null);
1383
* Returns whether a named property is defined in an object or any object
1384
* in its prototype chain.
1386
* Searches the prototype chain for a property named <code>name</code>.
1388
* @param obj a JavaScript object
1389
* @param name a property name
1390
* @return the true if property was found
1393
public static boolean hasProperty(Scriptable obj, String name) {
1394
Scriptable start = obj;
1396
if (obj.has(name, start))
1398
obj = obj.getPrototype();
1399
} while (obj != null);
1404
* Returns whether an indexed property is defined in an object or any object
1405
* in its prototype chain.
1407
* Searches the prototype chain for a property with index <code>index</code>.
1409
* @param obj a JavaScript object
1410
* @param index a property index
1411
* @return the true if property was found
1414
public static boolean hasProperty(Scriptable obj, int index) {
1415
Scriptable start = obj;
1417
if (obj.has(index, start))
1419
obj = obj.getPrototype();
1420
} while (obj != null);
1425
* Puts a named property in an object or in an object in its prototype chain.
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
1435
public static void putProperty(Scriptable obj, String name, Object value) {
1436
Scriptable base = getBase(obj, name);
1439
base.put(name, obj, value);
1443
* Puts an indexed property in an object or in an object in its prototype chain.
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
1453
public static void putProperty(Scriptable obj, int index, Object value) {
1454
Scriptable base = getBase(obj, index);
1457
base.put(index, obj, value);
1461
* Removes the property from an object or its prototype chain.
1463
* Searches for a property with <code>name</code> in obj or
1464
* its prototype chain. If it is found, the object's delete
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
1471
public static boolean deleteProperty(Scriptable obj, String name) {
1472
Scriptable base = getBase(obj, name);
1476
return base.get(name, obj) == NOT_FOUND;
1480
* Removes the property from an object or its prototype chain.
1482
* Searches for a property with <code>index</code> in obj or
1483
* its prototype chain. If it is found, the object's delete
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
1490
public static boolean deleteProperty(Scriptable obj, int index) {
1491
Scriptable base = getBase(obj, index);
1495
return base.get(index, obj) == NOT_FOUND;
1499
* Returns an array of all ids from an object and its prototypes.
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.
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]);
1514
obj = (Scriptable)obj.getPrototype();
1516
Object[] result = new Object[h.size()];
1517
java.util.Enumeration e = h.elements();
1519
while (e.hasMoreElements()) {
1520
result[n++] = e.nextElement();
1526
* Call a method of an object.
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
1533
public static Object callMethod(Scriptable obj, String methodName,
1535
throws JavaScriptException
1537
Context cx = Context.enter();
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));
1548
private static Scriptable getBase(Scriptable obj, String s) {
1553
m = m.getPrototype();
1558
private static Scriptable getBase(Scriptable obj, int index) {
1561
if (m.has(index, obj))
1563
m = m.getPrototype();
1569
* Adds a property attribute to all properties.
1571
synchronized void addPropertyAttribute(int attribute) {
1574
for (int i=0; i < slots.length; i++) {
1575
Slot slot = slots[i];
1576
if (slot == null || slot == REMOVED)
1578
if ((slot.flags & slot.HAS_SETTER) != 0 && attribute == READONLY)
1580
slot.attributes |= attribute;
1584
private Slot getSlot(String id, int index, boolean shouldDelete) {
1585
Slot[] slots = this.slots;
1588
int start = (index & 0x7fffffff) % slots.length;
1591
Slot slot = slots[i];
1594
if (slot != REMOVED && slot.intKey == index &&
1595
(slot.stringKey == id || (id != null &&
1596
id.equals(slot.stringKey))))
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;
1606
if (slot == lastAccess)
1607
lastAccess = REMOVED;
1612
if (++i == slots.length)
1614
} while (i != start);
1618
private Slot getSlotToSet(String id, int index, boolean getterSlot) {
1620
slots = new Slot[5];
1621
int start = (index & 0x7fffffff) % slots.length;
1622
boolean sawRemoved = false;
1625
Slot slot = slots[i];
1627
return addSlot(id, index, getterSlot);
1629
if (slot == REMOVED) {
1631
} else if (slot.intKey == index &&
1632
(slot.stringKey == id ||
1633
(id != null && id.equals(slot.stringKey))))
1637
if (++i == slots.length)
1639
} while (i != start);
1641
// Table could be full, but with some REMOVED elements.
1642
// Call to addSlot will use a slot currently taken by
1644
return addSlot(id, index, getterSlot);
1646
throw new RuntimeException("Hashtable internal error");
1650
* Add a new slot to the hash table.
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.
1657
private synchronized Slot addSlot(String id, int index, boolean getterSlot)
1660
throw Context.reportRuntimeError0("msg.add.sealed");
1661
int start = (index & 0x7fffffff) % slots.length;
1664
Slot slot = slots[i];
1665
if (slot == null || slot == REMOVED) {
1666
if ((4 * (count+1)) > (3 * slots.length)) {
1668
return getSlotToSet(id, index, getterSlot);
1670
slot = getterSlot ? new GetterSlot() : new Slot();
1671
slot.stringKey = id;
1672
slot.intKey = index;
1677
if (slot.intKey == index &&
1678
(slot.stringKey == id || (id != null &&
1679
id.equals(slot.stringKey))))
1683
if (++i == slots.length)
1685
} while (i != start);
1686
throw new RuntimeException("Hashtable internal error");
1690
* Remove a slot from the hash table.
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.
1696
private synchronized void removeSlot(String name, int index) {
1698
throw Context.reportRuntimeError0("msg.remove.sealed");
1699
getSlot(name, index, true);
1703
* Grow the hash table to accommodate new entries.
1705
* Note that by assigning the new array back at the end we
1706
* can continue reading the array from other threads.
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)
1714
int k = (slot.intKey & 0x7fffffff) % newSlots.length;
1715
while (newSlots[k] != null)
1716
if (++k == newSlots.length)
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
1723
//synchronized (slot) { }
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);
1737
exclusionList = result;
1741
Object[] getIds(boolean getAll) {
1743
Object[] a = ScriptRuntime.emptyArgs;
1747
for (int i=0; i < s.length; i++) {
1749
if (slot == null || slot == REMOVED)
1751
if (getAll || (slot.attributes & DONTENUM) == 0) {
1753
a = new Object[s.length - i];
1754
a[c++] = slot.stringKey != null
1755
? (Object) slot.stringKey
1756
: new Integer(slot.intKey);
1761
Object[] result = new Object[c];
1762
System.arraycopy(a, 0, result, 0, c);
1768
* The prototype of this object.
1770
protected Scriptable prototype;
1773
* The parent scope of this object.
1775
protected Scriptable parent;
1777
private static final Object HAS_STATIC_ACCESSORS = Void.TYPE;
1778
private static final Slot REMOVED = new Slot();
1779
private static Hashtable exclusionList = null;
1781
private Slot[] slots;
1784
// cache; may be removed for smaller memory footprint
1785
private Slot lastAccess = REMOVED;
1787
private static class Slot {
1788
static final int HAS_GETTER = 0x01;
1789
static final int HAS_SETTER = 0x02;
1799
private static class GetterSlot extends Slot {
1800
Object delegateTo; // OPT: merge with "value"
1803
boolean setterReturnsValue;
1806
private static final Class ContextClass = Context.class;
1
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
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/
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.
13
* The Original Code is Rhino code, released
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
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.
40
package org.mozilla.javascript;
42
import java.lang.reflect.*;
43
import java.util.Hashtable;
45
import org.mozilla.javascript.debug.DebuggableObject;
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.
52
* Various properties and methods of JavaScript objects can be conveniently
53
* defined using methods of ScriptableObject.
55
* Classes extending ScriptableObject must define the getClassName method.
57
* @see org.mozilla.javascript.Scriptable
61
public abstract class ScriptableObject implements Scriptable, Serializable,
66
* The empty property attribute.
68
* Used by getAttributes() and setAttributes().
70
* @see org.mozilla.javascript.ScriptableObject#getAttributes
71
* @see org.mozilla.javascript.ScriptableObject#setAttributes
73
public static final int EMPTY = 0x00;
76
* Property attribute indicating assignment to this property is ignored.
78
* @see org.mozilla.javascript.ScriptableObject#put
79
* @see org.mozilla.javascript.ScriptableObject#getAttributes
80
* @see org.mozilla.javascript.ScriptableObject#setAttributes
82
public static final int READONLY = 0x01;
85
* Property attribute indicating property is not enumerated.
87
* Only enumerated properties will be returned by getIds().
89
* @see org.mozilla.javascript.ScriptableObject#getIds
90
* @see org.mozilla.javascript.ScriptableObject#getAttributes
91
* @see org.mozilla.javascript.ScriptableObject#setAttributes
93
public static final int DONTENUM = 0x02;
96
* Property attribute indicating property cannot be deleted.
98
* @see org.mozilla.javascript.ScriptableObject#delete
99
* @see org.mozilla.javascript.ScriptableObject#getAttributes
100
* @see org.mozilla.javascript.ScriptableObject#setAttributes
102
public static final int PERMANENT = 0x04;
104
static void checkValidAttributes(int attributes)
106
final int mask = READONLY | DONTENUM | PERMANENT;
107
if ((attributes & ~mask) != 0) {
108
throw new IllegalArgumentException(String.valueOf(attributes));
112
public ScriptableObject()
116
public ScriptableObject(Scriptable scope, Scriptable prototype)
119
throw new IllegalArgumentException();
121
parentScopeObject = scope;
122
prototypeObject = prototype;
126
* Return the name of the class.
128
* This is typically the same name as the constructor.
129
* Classes extending ScriptableObject must implement this abstract
132
public abstract String getClassName();
135
* Returns true if the named property is defined.
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
141
public boolean has(String name, Scriptable start)
143
return null != getNamedSlot(name);
147
* Returns true if the property index is defined.
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
153
public boolean has(int index, Scriptable start)
155
return null != getSlot(null, index);
159
* Returns the value of the named property or NOT_FOUND.
161
* If the property was created using defineProperty, the
162
* appropriate getter method is called.
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
168
public Object get(String name, Scriptable start)
170
Slot slot = getNamedSlot(name);
172
return Scriptable.NOT_FOUND;
174
if (slot instanceof GetterSlot) {
175
GetterSlot gslot = (GetterSlot)slot;
176
if (gslot.getter != null) {
177
return getByGetter(gslot, start);
185
* Returns the value of the indexed property or NOT_FOUND.
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
191
public Object get(int index, Scriptable start)
193
Slot slot = getSlot(null, index);
195
return Scriptable.NOT_FOUND;
201
* Sets the value of the named property, creating it if need be.
203
* If the property was created using defineProperty, the
204
* appropriate setter method is called. <p>
206
* If the property's attributes include READONLY, no action is
208
* This method will actually set the property in the start
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
215
public void put(String name, Scriptable start, Object value)
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);
223
start.put(name, start, value);
226
slot = addSlot(name, hash, null);
228
// Note: cache is not updated in put
230
if (start == this && isSealed()) {
231
throw Context.reportRuntimeError1("msg.modify.sealed", name);
233
if ((slot.attributes & ScriptableObject.READONLY) != 0) {
236
if (slot instanceof GetterSlot) {
237
GetterSlot gslot = (GetterSlot)slot;
238
if (gslot.setter != null) {
239
setBySetter(gslot, start, value);
246
start.put(name, start, value);
251
* Sets the value of the indexed property, creating it if need be.
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
257
public void put(int index, Scriptable start, Object value)
259
Slot slot = getSlot(null, index);
262
start.put(index, start, value);
265
slot = addSlot(null, index, null);
267
if (start == this && isSealed()) {
268
throw Context.reportRuntimeError1("msg.modify.sealed",
269
Integer.toString(index));
271
if ((slot.attributes & ScriptableObject.READONLY) != 0) {
277
start.put(index, start, value);
282
* Removes a named property from the object.
284
* If the property is not found, or it has the PERMANENT attribute,
285
* no action is taken.
287
* @param name the name of the property
289
public void delete(String name) {
290
removeSlot(name, name.hashCode());
294
* Removes the indexed property from the object.
296
* If the property is not found, or it has the PERMANENT attribute,
297
* no action is taken.
299
* @param index the numeric index for the property
301
public void delete(int index) {
302
removeSlot(null, index);
306
* @deprecated Use {@link #getAttributes(String name)}. The engine always
307
* ignored the start argument.
309
public final int getAttributes(String name, Scriptable start)
311
return getAttributes(name);
315
* @deprecated Use {@link #getAttributes(int index)}. The engine always
316
* ignored the start argument.
318
public final int getAttributes(int index, Scriptable start)
320
return getAttributes(index);
324
* @deprecated Use {@link #setAttributes(String name, int attributes)}.
325
* The engine always ignored the start argument.
327
public final void setAttributes(String name, Scriptable start,
330
setAttributes(name, attributes);
334
* @deprecated Use {@link #setAttributes(int index, int attributes)}.
335
* The engine always ignored the start argument.
337
public void setAttributes(int index, Scriptable start,
340
setAttributes(index, attributes);
344
* Get the attributes of a named property.
346
* The property is specified by <code>name</code>
347
* as defined for <code>has</code>.<p>
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
358
public int getAttributes(String name)
360
Slot slot = getNamedSlot(name);
362
throw Context.reportRuntimeError1("msg.prop.not.found", name);
364
return slot.attributes;
368
* Get the attributes of an indexed property.
370
* @param index the numeric index for the property
371
* @exception EvaluatorException if the named property 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
380
public int getAttributes(int index)
382
Slot slot = getSlot(null, index);
384
throw Context.reportRuntimeError1("msg.prop.not.found",
385
String.valueOf(index));
387
return slot.attributes;
391
* Set the attributes of a named property.
393
* The property is specified by <code>name</code>
394
* as defined for <code>has</code>.<p>
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.
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
411
public void setAttributes(String name, int attributes)
413
checkValidAttributes(attributes);
414
Slot slot = getNamedSlot(name);
416
throw Context.reportRuntimeError1("msg.prop.not.found", name);
418
slot.attributes = (short) attributes;
422
* Set the attributes of an indexed property.
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
433
public void setAttributes(int index, int attributes)
435
checkValidAttributes(attributes);
436
Slot slot = getSlot(null, index);
438
throw Context.reportRuntimeError1("msg.prop.not.found",
439
String.valueOf(index));
441
slot.attributes = (short) attributes;
445
* Returns the prototype of the object.
447
public Scriptable getPrototype()
449
return prototypeObject;
453
* Sets the prototype of the object.
455
public void setPrototype(Scriptable m)
461
* Returns the parent (enclosing) scope of the object.
463
public Scriptable getParentScope()
465
return parentScopeObject;
469
* Sets the parent (enclosing) scope of the object.
471
public void setParentScope(Scriptable m)
473
parentScopeObject = m;
477
* Returns an array of ids for the properties of the object.
479
* <p>Any properties with the attribute DONTENUM are not listed. <p>
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.
487
public Object[] getIds() {
488
return getIds(false);
492
* Returns an array of ids for the properties of the object.
494
* <p>All properties, even those with attribute DONTENUM, are listed. <p>
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.
502
public Object[] getAllIds() {
507
* Implements the [[DefaultValue]] internal method.
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>
513
* A <code>hint</code> of null means "no hint".
515
* @param typeHint the type hint
516
* @return the default value for the object
520
public Object getDefaultValue(Class typeHint)
523
for (int i=0; i < 2; i++) {
525
if (typeHint == ScriptRuntime.StringClass) {
526
tryToString = (i == 0);
528
tryToString = (i == 1);
534
methodName = "toString";
535
args = ScriptRuntime.emptyArgs;
537
methodName = "valueOf";
538
args = new Object[1];
540
if (typeHint == null) {
542
} else if (typeHint == ScriptRuntime.StringClass) {
544
} else if (typeHint == ScriptRuntime.ScriptableClass) {
546
} else if (typeHint == ScriptRuntime.FunctionClass) {
548
} else if (typeHint == ScriptRuntime.BooleanClass
549
|| typeHint == Boolean.TYPE)
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)
566
throw Context.reportRuntimeError1(
567
"msg.invalid.type", typeHint.toString());
571
Object v = getProperty(this, methodName);
572
if (!(v instanceof Function))
574
Function fun = (Function) v;
576
cx = Context.getContext();
577
v = fun.call(cx, fun.getParentScope(), this, args);
579
if (!(v instanceof Scriptable)) {
582
if (v == Undefined.instance
583
|| typeHint == ScriptRuntime.ScriptableClass
584
|| typeHint == ScriptRuntime.FunctionClass)
588
if (tryToString && v instanceof Wrapper) {
589
// Let a wrapped java.lang.String pass for a primitive
591
Object u = ((Wrapper)v).unwrap();
592
if (u instanceof String)
597
// fall through to error
598
String arg = (typeHint == null) ? "undefined" : typeHint.getName();
599
throw ScriptRuntime.typeError1("msg.default.value", arg);
603
* Implements the instanceof operator.
605
* <p>This operator has been proposed to ECMA.
607
* @param instance The value that appeared on the LHS of the instanceof
609
* @return true if "this" appears in value's prototype chain
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
617
return ScriptRuntime.jsDelegatesTo(instance, this);
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
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.
633
protected Object equivalentValues(Object value)
635
return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND;
639
* Defines JavaScript objects from a Java class that implements Scriptable.
641
* If the given class has a method
643
* static void init(Context cx, Scriptable scope, boolean sealed);</pre>
645
* or its compatibility form
647
* static void init(Scriptable scope);</pre>
649
* then it is invoked and no further initialization is done.<p>
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>
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>
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
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
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>
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.
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>
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}
705
* Finally, if there is a method
707
* static void finishInit(Scriptable scope, FunctionObject constructor,
708
* Scriptable prototype)</pre>
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>
714
* @param scope The scope in which to define the constructor
715
* @param clazz The Java class to use to define the JavaScript objects
717
* @exception IllegalAccessException if access is not available
718
* to a reflected class member
719
* @exception InstantiationException if unable to instantiate
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
728
public static void defineClass(Scriptable scope, Class clazz)
729
throws IllegalAccessException, InstantiationException,
730
InvocationTargetException
732
defineClass(scope, clazz, false);
736
* Defines JavaScript objects from a Java class, optionally
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
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
754
* @exception InvocationTargetException if an exception is thrown
755
* during execution of methods of the named class
758
public static void defineClass(Scriptable scope, Class clazz,
760
throws IllegalAccessException, InstantiationException,
761
InvocationTargetException
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"))
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()))
775
Object args[] = { Context.getContext(), scope,
776
sealed ? Boolean.TRUE : Boolean.FALSE };
777
method.invoke(null, args);
780
if (parmTypes.length == 1 &&
781
parmTypes[0] == ScriptRuntime.ScriptableClass &&
782
Modifier.isStatic(method.getModifiers()))
784
Object args[] = { scope };
785
method.invoke(null, args);
791
// If we got here, there isn't an "init" method with the right
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];
802
if (protoCtor == null) {
803
throw Context.reportRuntimeError1(
804
"msg.zero.arg.ctor", clazz.getName());
807
Scriptable proto = (Scriptable)
808
protoCtor.newInstance(ScriptRuntime.emptyArgs);
809
proto.setPrototype(getObjectPrototype(scope));
810
String className = proto.getClassName();
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";
821
Member ctorMember = FunctionObject.findSingleMethod(methods, ctorName);
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];
832
if (ctorMember == null) {
833
throw Context.reportRuntimeError1(
834
"msg.ctor.multiple.parms", clazz.getName());
838
FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
839
if (ctor.isVarArgsMethod()) {
840
throw Context.reportRuntimeError1
841
("msg.varargs.ctor", ctorMember.getName());
843
ctor.addAsConstructor(scope, proto);
845
Method finishInit = null;
846
for (int i=0; i < methods.length; i++) {
847
if (methods[i] == ctorMember) {
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()))
859
finishInit = methods[i];
863
// ignore any compiler generated methods.
864
if (name.indexOf('$') != -1)
866
if (name.equals(ctorName))
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.");
878
} else if (name.startsWith(getterPrefix)) {
879
prefix = getterPrefix;
880
} else if (name.startsWith(setterPrefix)) {
881
prefix = setterPrefix;
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);
894
Method setter = FunctionObject.findSingleMethod(
896
setterPrefix + name);
897
int attr = ScriptableObject.PERMANENT |
898
ScriptableObject.DONTENUM |
900
: ScriptableObject.READONLY);
901
((ScriptableObject) proto).defineProperty(name, null,
907
FunctionObject f = new FunctionObject(name, methods[i], proto);
908
if (f.isVarArgsConstructor()) {
909
throw Context.reportRuntimeError1
910
("msg.varargs.fun", ctorMember.getName());
912
Scriptable dest = prefix == staticFunctionPrefix
915
defineProperty(dest, name, f, DONTENUM);
921
if (finishInit != null) {
922
// call user code to complete the initialization
923
Object[] finishArgs = { scope, ctor, proto };
924
finishInit.invoke(null, finishArgs);
929
if (proto instanceof ScriptableObject) {
930
((ScriptableObject) proto).sealObject();
936
* Define a JavaScript property.
938
* Creates the property with an initial value and sets its attributes.
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
945
public void defineProperty(String propertyName, Object value,
948
put(propertyName, this, value);
949
setAttributes(propertyName, attributes);
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
958
public static void defineProperty(Scriptable destination,
959
String propertyName, Object value,
962
if (!(destination instanceof ScriptableObject)) {
963
destination.put(propertyName, destination, value);
966
ScriptableObject so = (ScriptableObject)destination;
967
so.defineProperty(propertyName, value, attributes);
971
* Define a JavaScript property with getter and setter side effects.
973
* If the setter is not found, the attribute READONLY is added to
974
* the given attributes. <p>
976
* The getter must be a method with zero parameters, and the setter, if
977
* found, must be a method with one parameter.<p>
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
988
public void defineProperty(String propertyName, Class clazz,
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]);
999
String getterName = new String(buf);
1001
String setterName = new String(buf);
1003
Method[] methods = FunctionObject.getMethodList(clazz);
1004
Method getter = FunctionObject.findSingleMethod(methods, getterName);
1005
Method setter = FunctionObject.findSingleMethod(methods, setterName);
1007
attributes |= ScriptableObject.READONLY;
1008
defineProperty(propertyName, null, getter,
1009
setter == null ? null : setter, attributes);
1013
* Define a JavaScript property.
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>.
1020
* If <code>setter</code> is null, the attribute READONLY is added to
1021
* the given attributes.<p>
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
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:
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:
1043
* Object getFoo(ScriptableObject obj);
1044
* void setFoo(ScriptableObject obj, SomeType value);</pre>
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
1053
public void defineProperty(String propertyName, Object delegateTo,
1054
Method getter, Method setter, int attributes)
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)
1063
throw Context.reportRuntimeError1(
1064
"msg.bad.getter.parms", getter.toString());
1066
} else if (delegateTo != null) {
1067
throw Context.reportRuntimeError1(
1068
"msg.obj.getter.parms", getter.toString());
1070
if (setter != null) {
1071
if ((delegateTo == HAS_STATIC_ACCESSORS) !=
1072
(Modifier.isStatic(setter.getModifiers())))
1074
throw Context.reportRuntimeError0("msg.getter.static");
1076
parmTypes = setter.getParameterTypes();
1077
if (parmTypes.length == 2) {
1078
if (parmTypes[0] != ScriptRuntime.ScriptableObjectClass) {
1079
throw Context.reportRuntimeError0("msg.setter2.parms");
1081
if (delegateTo == null) {
1082
throw Context.reportRuntimeError1(
1083
"msg.setter1.parms", setter.toString());
1085
} else if (parmTypes.length == 1) {
1086
if (delegateTo != null) {
1087
throw Context.reportRuntimeError1(
1088
"msg.setter2.expected", setter.toString());
1091
throw Context.reportRuntimeError0("msg.setter.parms");
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(),
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();
1111
gslot.attributes = (short) attributes;
1112
Slot inserted = addSlot(propertyName, propertyName.hashCode(), gslot);
1113
if (inserted != gslot) {
1114
throw new RuntimeException("Property already exists");
1120
* Search for names in a class, adding the resulting methods
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.
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
1132
public void defineFunctionProperties(String[] names, Class clazz,
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);
1140
throw Context.reportRuntimeError2(
1141
"msg.method.not.found", name, clazz.getName());
1143
FunctionObject f = new FunctionObject(name, m, this);
1144
defineProperty(name, f, attributes);
1149
* Get the Object.prototype property.
1152
public static Scriptable getObjectPrototype(Scriptable scope) {
1153
return getClassPrototype(scope, "Object");
1157
* Get the Function.prototype property.
1160
public static Scriptable getFunctionPrototype(Scriptable scope) {
1161
return getClassPrototype(scope, "Function");
1165
* Get the prototype for the named class.
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
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
1179
public static Scriptable getClassPrototype(Scriptable scope,
1182
scope = getTopLevelScope(scope);
1183
Object ctor = getProperty(scope, className);
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);
1193
if (proto instanceof Scriptable) {
1194
return (Scriptable)proto;
1200
* Get the global scope.
1202
* <p>Walks the parent scope chain to find an object with a null
1203
* parent scope (the global object).
1205
* @param obj a JavaScript object
1206
* @return the corresponding global scope
1208
public static Scriptable getTopLevelScope(Scriptable obj)
1211
Scriptable parent = obj.getParentScope();
1212
if (parent == null) {
1222
* A sealed object may not have properties added or removed. Once
1223
* an object is sealed it may not be unsealed.
1227
public synchronized void sealObject() {
1234
* Return true if this object is sealed.
1236
* It is an error to attempt to add or remove properties to
1239
* @return true if sealed, false otherwise.
1242
public final boolean isSealed() {
1247
* Gets a named property from an object or any object in its prototype chain.
1249
* Searches the prototype chain for a property named <code>name</code>.
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
1258
public static Object getProperty(Scriptable obj, String name)
1260
Scriptable start = obj;
1263
result = obj.get(name, start);
1264
if (result != Scriptable.NOT_FOUND)
1266
obj = obj.getPrototype();
1267
} while (obj != null);
1272
* Gets an indexed property from an object or any object in its prototype chain.
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.
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
1286
public static Object getProperty(Scriptable obj, int index)
1288
Scriptable start = obj;
1291
result = obj.get(index, start);
1292
if (result != Scriptable.NOT_FOUND)
1294
obj = obj.getPrototype();
1295
} while (obj != null);
1300
* Returns whether a named property is defined in an object or any object
1301
* in its prototype chain.
1303
* Searches the prototype chain for a property named <code>name</code>.
1305
* @param obj a JavaScript object
1306
* @param name a property name
1307
* @return the true if property was found
1310
public static boolean hasProperty(Scriptable obj, String name)
1312
return null != getBase(obj, name);
1316
* Returns whether an indexed property is defined in an object or any object
1317
* in its prototype chain.
1319
* Searches the prototype chain for a property with index <code>index</code>.
1321
* @param obj a JavaScript object
1322
* @param index a property index
1323
* @return the true if property was found
1326
public static boolean hasProperty(Scriptable obj, int index)
1328
return null != getBase(obj, index);
1332
* Puts a named property in an object or in an object in its prototype chain.
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
1342
public static void putProperty(Scriptable obj, String name, Object value)
1344
Scriptable base = getBase(obj, name);
1347
base.put(name, obj, value);
1351
* Puts an indexed property in an object or in an object in its prototype chain.
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
1361
public static void putProperty(Scriptable obj, int index, Object value)
1363
Scriptable base = getBase(obj, index);
1366
base.put(index, obj, value);
1370
* Removes the property from an object or its prototype chain.
1372
* Searches for a property with <code>name</code> in obj or
1373
* its prototype chain. If it is found, the object's delete
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
1380
public static boolean deleteProperty(Scriptable obj, String name)
1382
Scriptable base = getBase(obj, name);
1386
return !base.has(name, obj);
1390
* Removes the property from an object or its prototype chain.
1392
* Searches for a property with <code>index</code> in obj or
1393
* its prototype chain. If it is found, the object's delete
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
1400
public static boolean deleteProperty(Scriptable obj, int index)
1402
Scriptable base = getBase(obj, index);
1406
return !base.has(index, obj);
1410
* Returns an array of all ids from an object and its prototypes.
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.
1418
public static Object[] getPropertyIds(Scriptable obj)
1421
return ScriptRuntime.emptyArgs;
1423
Object[] result = obj.getIds();
1424
ObjToIntMap map = null;
1426
obj = obj.getPrototype();
1430
Object[] ids = obj.getIds();
1431
if (ids.length == 0) {
1435
if (result.length == 0) {
1439
map = new ObjToIntMap(result.length + ids.length);
1440
for (int i = 0; i != result.length; ++i) {
1441
map.intern(result[i]);
1443
result = null; // Allow to GC the result
1445
for (int i = 0; i != ids.length; ++i) {
1450
result = map.getKeys();
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
1461
* @see Context#getCurrentContext()
1463
public static Object callMethod(Scriptable obj, String methodName,
1466
return callMethod(null, obj, methodName, args);
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
1476
public static Object callMethod(Context cx, Scriptable obj,
1480
Object funObj = getProperty(obj, methodName);
1481
if (!(funObj instanceof Function)) {
1482
throw ScriptRuntime.notFunctionError(obj, methodName);
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);
1494
return fun.call(cx, scope, obj, args);
1496
return Context.call(null, fun, scope, obj, args);
1500
private static Scriptable getBase(Scriptable obj, String name)
1503
if (obj.has(name, obj))
1505
obj = obj.getPrototype();
1506
} while(obj != null);
1510
private static Scriptable getBase(Scriptable obj, int index)
1513
if (obj.has(index, obj))
1515
obj = obj.getPrototype();
1516
} while(obj != null);
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)
1525
public final Object getAssociatedValue(Object key)
1527
Hashtable h = associatedValues;
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.
1540
* @param scope the starting scope.
1541
* @param key key object to select particular value.
1542
* @see #getAssociatedValue(Object key)
1544
public static Object getTopScopeValue(Scriptable scope, Object key)
1546
scope = ScriptableObject.getTopLevelScope(scope);
1548
if (scope instanceof ScriptableObject) {
1549
ScriptableObject so = (ScriptableObject)scope;
1550
Object value = so.getAssociatedValue(key);
1551
if (value != null) {
1555
scope = scope.getPrototype();
1556
if (scope == null) {
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
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)
1574
public final Object associateValue(Object key, Object value)
1576
if (value == null) throw new IllegalArgumentException();
1577
Hashtable h = associatedValues;
1579
synchronized (this) {
1580
h = associatedValues;
1582
h = new Hashtable();
1583
associatedValues = h;
1587
return Kit.initHash(h, key, value);
1590
private Object getByGetter(GetterSlot slot, Scriptable start)
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) {
1604
if (start == null) {
1611
args = ScriptRuntime.emptyArgs;
1613
getterThis = slot.delegateTo;
1614
args = new Object[] { this };
1617
return slot.getter.invoke(getterThis, args);
1620
private void setBySetter(GetterSlot slot, Scriptable start, Object value)
1622
if (start != this) {
1623
if (slot.delegateTo != null
1624
|| !slot.setter.getDeclaringClass().isInstance(start))
1626
start.put(slot.stringKey, start, value);
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) {
1642
args = new Object[] { actualArg };
1644
if (start != this) Kit.codeBug();
1645
setterThis = slot.delegateTo;
1646
args = new Object[] { this, actualArg };
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",
1655
setterResult = slot.setter.invoke(setterThis, args);
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;
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;
1679
private Slot getNamedSlot(String name)
1681
// Query last access cache and check that it was not deleted
1682
Slot slot = lastAccess;
1683
if (name == slot.stringKey && slot.wasDeleted == 0) {
1686
int hash = name.hashCode();
1687
Slot[] slots = this.slots; // Get stable local reference
1688
int i = getSlotPosition(slots, name, hash);
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;
1701
private Slot getSlot(String id, int index)
1703
Slot[] slots = this.slots; // Get local copy
1704
int i = getSlotPosition(slots, id, index);
1705
return (i < 0) ? null : slots[i];
1708
private static int getSlotPosition(Slot[] slots, String id, int index)
1710
if (slots != null) {
1711
int start = (index & 0x7fffffff) % slots.length;
1714
Slot slot = slots[i];
1717
if (slot != REMOVED && slot.intKey == index
1718
&& (slot.stringKey == id
1719
|| (id != null && id.equals(slot.stringKey))))
1723
if (++i == slots.length)
1725
} while (i != start);
1731
* Add a new slot to the hash table.
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.
1738
private synchronized Slot addSlot(String id, int index, Slot newSlot) {
1740
String str = (id != null) ? id : Integer.toString(index);
1741
throw Context.reportRuntimeError1("msg.add.sealed", str);
1744
if (slots == null) { slots = new Slot[5]; }
1746
return addSlotImpl(id, index, newSlot);
1749
// Must be inside synchronized (this)
1750
private Slot addSlotImpl(String id, int index, Slot newSlot)
1752
int start = (index & 0x7fffffff) % slots.length;
1755
Slot slot = slots[i];
1756
if (slot == null || slot == REMOVED) {
1757
if ((4 * (count + 1)) > (3 * slots.length)) {
1759
return addSlotImpl(id, index, newSlot);
1761
slot = (newSlot == null) ? new Slot() : newSlot;
1762
slot.stringKey = id;
1763
slot.intKey = index;
1768
if (slot.intKey == index &&
1769
(slot.stringKey == id || (id != null &&
1770
id.equals(slot.stringKey))))
1774
if (++i == slots.length)
1777
// slots should never be full or bug in grow code
1778
throw new IllegalStateException();
1784
* Remove a slot from the hash table.
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.
1790
private synchronized void removeSlot(String name, int index) {
1792
String str = (name != null) ? name : Integer.toString(index);
1793
throw Context.reportRuntimeError1("msg.remove.sealed", str);
1796
int i = getSlotPosition(slots, name, index);
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;
1811
// With no slots it is OK to mark with null.
1818
// Grow the hash table to accommodate new entries.
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)
1829
int k = (slot.intKey & 0x7fffffff) % newSlots.length;
1830
while (newSlots[k] != null)
1831
if (++k == newSlots.length)
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
1838
//synchronized (slot) { }
1844
Object[] getIds(boolean getAll) {
1846
Object[] a = ScriptRuntime.emptyArgs;
1850
for (int i=0; i < s.length; i++) {
1852
if (slot == null || slot == REMOVED)
1854
if (getAll || (slot.attributes & DONTENUM) == 0) {
1856
a = new Object[s.length - i];
1857
a[c++] = slot.stringKey != null
1858
? (Object) slot.stringKey
1859
: new Integer(slot.intKey);
1864
Object[] result = new Object[c];
1865
System.arraycopy(a, 0, result, 0, c);
1869
private synchronized void writeObject(ObjectOutputStream out)
1872
out.defaultWriteObject();
1879
if (N != 0) Kit.codeBug();
1882
out.writeInt(s.length);
1883
for (int i = 0; N != 0; ++i) {
1885
if (slot != null && slot != REMOVED) {
1887
out.writeObject(slot);
1893
private void readObject(ObjectInputStream in)
1894
throws IOException, ClassNotFoundException
1896
in.defaultReadObject();
1897
lastAccess = REMOVED;
1899
int capacity = in.readInt();
1900
if (capacity != 0) {
1901
slots = new Slot[capacity];
1903
boolean wasSealed = false;
1905
N = -1 - N; wasSealed = true;
1908
for (int i = 0; i != N; ++i) {
1909
Slot s = (Slot)in.readObject();
1910
addSlotImpl(s.stringKey, s.intKey, s);
1913
count = - 1 - count;
1919
* The prototype of this object.
1921
private Scriptable prototypeObject;
1924
* The parent scope of this object.
1926
private Scriptable parentScopeObject;
1928
private static final Object HAS_STATIC_ACCESSORS = Void.TYPE;
1929
private static final Slot REMOVED = new Slot();
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
1936
// cache; may be removed for smaller memory footprint
1937
private transient Slot lastAccess = REMOVED;
1939
// associated values are not serialized
1940
private transient volatile Hashtable associatedValues;
1942
private static class Slot implements Serializable
1944
private void readObject(ObjectInputStream in)
1945
throws IOException, ClassNotFoundException
1947
in.defaultReadObject();
1948
if (stringKey != null) {
1949
intKey = stringKey.hashCode();
1957
transient byte wasDeleted;
1960
private static final class GetterSlot extends Slot